File manager - Edit - /home/xfekoga/grenier/wp-content/plugins/defender-security/src/component/two-factor/providers/class-totp.php
Back
<?php /** * Responsible for handling the Time-based One-Time Password (TOTP) authentication. * * @package WP_Defender\Component\Two_Factor\Providers */ namespace WP_Defender\Component\Two_Factor\Providers; use WP_User; use WP_Error; use SodiumException; use Calotes\Helper\HTTP; use WP_Defender\Traits\IO; use WP_Defender\Extra\Base2n; use WP_Defender\Component\Crypt; use WP_Defender\Model\Setting\Two_Fa; use WP_Defender\Component\Two_Fa as Two_Fa_Component; use WP_Defender\Component\Two_Factor\Two_Factor_Provider; /** * Manage Time-based One-Time Password (TOTP) authentication. * Note: key 'defenderAuthOn' only for TOTP method. * * @since 2.8.0 */ class Totp extends Two_Factor_Provider { use IO; /** * 2fa provider slug. * * @var string */ public static $slug = 'totp'; /** * Meta key to check if TOTP is enabled for a user. * * @var string */ public const TOTP_AUTH_KEY = 'defenderAuthOn'; /** * Used def.key before v3.4.0. * * @var string */ public const TOTP_SECRET_KEY = 'defenderAuthSecret'; /** * Use Sodium library since v3.4.0. * * @var string */ public const TOTP_SODIUM_SECRET_KEY = 'defenderAuthSodiumSecret'; /** * Meta key to force TOTP authentication. * * @var string */ public const TOTP_FORCE_KEY = 'defenderForceAuth'; /** * Number of digits in the TOTP code. * * @var int */ public const TOTP_DIGIT_COUNT = 6; /** * Time step in seconds for TOTP code validity. * * @var int */ public const TOTP_TIME_STEP_SEC = 30; /** * Length of the TOTP secret key. * * @var int */ public const TOTP_LENGTH = 16; /** * RFC 4648 base32 alphabet. * * @var string */ public const TOTP_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; /** * Default cryptographic method used for TOTP. * * @var string */ public const DEFAULT_CRYPTO = 'sha1'; /** * 1 = 30 second range for authenticator. * * @var int */ public const TOTP_TIME_STEP_ALLOWANCE = 1; /** * Label for the TOTP method. * * @var string */ protected $label; /** * Description for the TOTP method. * * @var string */ protected $description; /** * Constructor to add actions for initializing and managing user options for TOTP. */ public function __construct() { add_action( 'wd_2fa_init_provider_' . self::$slug, array( &$this, 'init_provider' ) ); add_action( 'wd_2fa_user_options_' . self::$slug, array( &$this, 'user_options' ) ); } /** * Get the name of the provider. * * @return string */ public function get_label(): string { return esc_html__( 'TOTP Authenticator App', 'defender-security' ) . $this->label; } /** * Returns the login label for the TOTP authentication method. * * @return string The login label for the TOTP authentication method. */ public function get_login_label(): string { return esc_html__( 'TOTP Authentication', 'defender-security' ); } /** * Returns the user label for the TOTP authentication method. * * @return string The user label for the TOTP authentication method. */ public function get_user_label(): string { return esc_html__( 'TOTP', 'defender-security' ); } /** * Get the desc of the provider. * * @return string */ public function get_description(): string { return $this->description; } /** * Renders the authentication form for TOTP. */ public function authentication_form() { ?> <p class="wpdef-2fa-label"><?php echo $this->get_login_label(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p> <p class="wpdef-2fa-text def-otp-text"><?php echo esc_html( $this->get_model()->app_text ); ?></p> <input type="text" autofocus value="" autocomplete="off" name="otp"/> <button class="button button-primary float-r" type="submit"> <?php esc_attr_e( 'Authenticate', 'defender-security' ); ?> </button> <?php } /** * Returns a list of supported authentication apps. * * @return array List of supported authentication apps. */ public function get_auth_apps(): array { return array( 'google-authenticator' => 'Google Authenticator', 'microsoft-authenticator' => 'Microsoft Authenticator', 'authy' => 'Authy', ); } /** * Initializes the provider for a specific user. * * @param WP_User $user The user object. */ public function init_provider( WP_User $user ) { $is_on = $this->is_available_for_user( $user ); // $this->label will be escaped at src\view\two-fa\user-options.php. $this->label = $is_on ? sprintf( '<button type="button" class="button reset-totp-keys button-secondary hide-if-no-js" %s>%s</button>', $this->get_component()->is_checked_enabled_provider_by_slug( $user, self::$slug ) ? '' : ' disabled', esc_html__( 'Reset Keys', 'defender-security' ) ) : ''; $this->description = $is_on ? esc_html__( 'TOTP Authentication method is active for this site', 'defender-security' ) : esc_html__( 'Use an authenticator app to sign in with a separate passcode.', 'defender-security' ); } /** * Display auth method. * * @param WP_User $user The user object. * * @throws SodiumException Exceptions thrown by the sodium functions. */ public function user_options( WP_User $user ) { // Enqueue scripts. if ( ! wp_script_is( 'clipboard', 'enqueued' ) ) { wp_enqueue_script( 'clipboard' ); } wp_enqueue_script( 'def-qrcode', defender_asset_url( '/assets/js/jquery-qrcode.min.js' ), array( 'jquery' ), '0.18.0', true ); $model = $this->get_model(); $service = $this->get_component(); $default_values = $model->get_default_values(); $is_on = $this->is_available_for_user( $user ); if ( $is_on ) { $this->get_controller()->render_partial( 'two-fa/providers/totp-enabled', array( 'url' => $this->get_url( 'disable_totp' ), ) ); } else { $is_success = true; $result = self::get_user_secret( $user->ID ); if ( is_wp_error( $result ) ) { $secret = $result->get_error_message(); $is_success = false; } elseif ( is_bool( $result ) ) { // Sometimes we can get a boolean value due to errors with writing to the database. In this case, we need to reset the value. delete_user_meta( $user->ID, self::TOTP_SECRET_KEY ); // Also for new key. delete_user_meta( $user->ID, self::TOTP_SODIUM_SECRET_KEY ); $secret = self::get_user_secret( $user->ID ); } else { $secret = $result; } $this->get_controller()->render_partial( 'two-fa/providers/totp-disabled', array( 'url' => $this->get_url( 'verify_otp_for_enabling' ), 'default_message' => $default_values['message'], 'auth_apps' => $this->get_auth_apps(), 'user' => $user, 'secret_key' => $secret, 'class' => $service->is_checked_enabled_provider_by_slug( $user, self::$slug ) ? '' : 'hidden', 'is_success' => $is_success, ) ); } } /** * Generate a QR code for apps can use. Apps from get_auth_apps(). * * @param string $secret_key The secret key for the TOTP. * * @retun string */ public static function generate_qr_code( $secret_key ) { $settings = new Two_Fa(); $issuer = rawurlencode( $settings->app_title ); $user = wp_get_current_user(); return 'otpauth://totp/' . $issuer . ':' . rawurlencode( $user->user_email ) . '?secret=' . $secret_key . '&issuer=' . $issuer; } /** * Whether this 2FA provider is configured and available for the user specified. * * @param WP_User $user WP_User object of the logged-in user. * * @return boolean */ public function is_available_for_user( WP_User $user ) { return (bool) get_user_meta( $user->ID, self::TOTP_AUTH_KEY, true ); } /** * Validates the TOTP authentication for a user. * * @param WP_User $user The user to validate. * * @return bool|WP_Error True if validation is successful, WP_Error otherwise. * @throws SodiumException Exceptions thrown by the sodium functions. */ public function validate_authentication( WP_User $user ) { $otp = HTTP::post( 'otp' ); if ( empty( $otp ) ) { $lockout_message = $this->get_component()->verify_attempt( $user->ID, self::$slug ); return new WP_Error( 'opt_fail', empty( $lockout_message ) ? esc_html__( 'Whoops, the passcode you entered was incorrect or expired.', 'defender-security' ) : $lockout_message ); } $setup_key = self::get_user_secret( $user->ID ); if ( is_wp_error( $setup_key ) ) { return new WP_Error( 'opt_fail', esc_html__( 'Whoops, the passcode you entered was incorrect or expired.', 'defender-security' ) ); } return self::verify_otp( $otp, $user->ID, $setup_key ); } /** * Retrieves the TOTP secret for a user. * * @param int $user_id The user ID. * * @return string|WP_Error The TOTP secret, or WP_Error on failure. * @throws SodiumException Exceptions thrown by the sodium functions. */ private static function get_user_secret( $user_id ) { // First, we check the new 'TOTP_SODIUM_SECRET_KEY' key. $data = get_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, true ); if ( ! empty( $data ) ) { return Crypt::get_decrypted_data( $data ); } // Then check the old 'TOTP_SECRET_KEY' key. if ( ( new Two_Fa_Component() )->maybe_update( $user_id ) ) { // Check a new key again. $data = get_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, true ); if ( ! empty( $data ) && is_string( $data ) ) { return Crypt::get_decrypted_data( $data ); } } // Finally, generate a new one. return defender_generate_random_string( self::TOTP_LENGTH, self::TOTP_CHARACTERS ); } /** * Saves the TOTP setup key for a user. * * @param int $user_id The user ID. * @param string $plaintext The plaintext TOTP key. * * @return bool|WP_Error True on success, WP_Error on failure. * @throws SodiumException Exceptions thrown by the sodium functions. */ public static function save_setup_key( $user_id, $plaintext ) { $secret = Crypt::get_encrypted_data( $plaintext ); if ( is_wp_error( $secret ) ) { return $secret; } update_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, $secret ); return true; } /** * Generate an OTP code base on current time. * * @param int $counter The time counter. * @param int $user_id The user ID. * @param string $setup_key The TOTP setup key. * * @return string|WP_Error * @throws SodiumException Exceptions thrown by the sodium functions. */ private static function generate_otp( $counter, $user_id, $setup_key ) { if ( empty( $setup_key ) || ! is_string( $setup_key ) ) { $setup_key = self::get_user_secret( $user_id ); if ( is_wp_error( $setup_key ) ) { return $setup_key; } } include_once defender_path( 'src/extra/binary-to-text-php/Base2n.php' ); $base32 = new Base2n( 5, self::TOTP_CHARACTERS, false, true, true ); $secret = $base32->decode( $setup_key ); $input = floor( $counter / self::TOTP_TIME_STEP_SEC ); // According to https://tools.ietf.org/html/rfc4226#section-5.3, should be 8 bytes value. $time = chr( 0 ) . chr( 0 ) . chr( 0 ) . chr( 0 ) . pack( 'N*', $input ); $hmac = hash_hmac( self::DEFAULT_CRYPTO, $time, $secret, true ); // Now we have 20 bytes of DEFAULT_CRYPTO, need to short it down. Getting last byte of the hmac. $offset = ord( substr( $hmac, - 1 ) ) & 0x0F; $four_bytes = substr( $hmac, $offset, 4 ); // Now convert it into INT. $value = unpack( 'N', $four_bytes ); $value = $value[1]; // Make sure it always actual like 32 bits. $value = $value & 0x7FFFFFFF; // Close. $code = $value % 10 ** self::TOTP_DIGIT_COUNT; // In some case we have the 0 before, so it becomes lesser than TOTP_DIGIT_COUNT, make sure it always right. return str_pad( (string) $code, self::TOTP_DIGIT_COUNT, '0', STR_PAD_LEFT ); } /** * Verify the OTP of beyond & after TOTP_TIME_STEP_SEC seconds windows. * * @param string $user_code The TOTP code entered by the user. * @param int $user_id The user ID. * @param string $setup_key The TOTP setup key. * * @return bool|WP_Error * @throws SodiumException Exceptions thrown by the sodium functions. */ public static function verify_otp( string $user_code, int $user_id, $setup_key ) { if ( strlen( $user_code ) < self::TOTP_DIGIT_COUNT ) { return false; } for ( $i = - self::TOTP_TIME_STEP_ALLOWANCE; $i <= self::TOTP_TIME_STEP_ALLOWANCE; $i++ ) { $counter = 0 === $i ? time() : $i * self::TOTP_TIME_STEP_SEC + time(); $code = self::generate_otp( $counter, $user_id, $setup_key ); if ( is_wp_error( $code ) ) { return $code; } if ( Crypt::compare_lines( $user_code, $code ) ) { return true; } } return false; } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Generation time: 0 |
proxy
|
phpinfo
|
Settings