Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full RFC6238 Compatibility #207

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions providers/class.two-factor-totp.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ public function validate_authentication( $user ) {
*
* @param string $key The share secret key to use.
* @param string $authcode The code to test.
* @param string $hash The hash used to calculate the code.
* @param int $time_step The size of the time step.
*
* @return bool Whether the code is valid within the time frame
*/
public static function is_valid_authcode( $key, $authcode ) {
public static function is_valid_authcode( $key, $authcode, $hash = 'sha1', $time_step = 30 ) {
/**
* Filter the maximum ticks to allow when checking valid codes.
*
Expand All @@ -196,9 +198,11 @@ public static function is_valid_authcode( $key, $authcode ) {

$time = time() / self::DEFAULT_TIME_STEP_SEC;

$digits = strlen( $authcode );

foreach ( $ticks as $offset ) {
$log_time = $time + $offset;
if ( self::calc_totp( $key, $log_time ) === $authcode ) {
if ( self::calc_totp( $key, $log_time, $digits, $hash, $time_step ) === $authcode ) {
kasparsd marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
}
Expand Down Expand Up @@ -249,6 +253,30 @@ public static function pack64( $value ) {
return pack( 'NN', $higher, $lower );
}

/**
* Pad a short secret with bytes from the same until it's the correct length
* for hashing.
*
* @param string $secret Secret key to pad.
* @param int $length Byte length of the desired padded secret.
*
* @throws InvalidArgumentException If the secret or length are invalid.
*
* @return string
*/
protected static function pad_secret( $secret, $length ) {
if ( empty( $secret ) ) {
throw new InvalidArgumentException( 'Secret must be non-empty!' );
}

$length = intval( $length );
if ( $length <= 0 ) {
throw new InvalidArgumentException( 'Padding length must be non-zero' );
}

return str_pad( $secret, $length, $secret, STR_PAD_RIGHT );
}

/**
* Calculate a valid code given the shared secret key
*
Expand All @@ -263,6 +291,20 @@ public static function pack64( $value ) {
public static function calc_totp( $key, $step_count = false, $digits = self::DEFAULT_DIGIT_COUNT, $hash = self::DEFAULT_CRYPTO, $time_step = self::DEFAULT_TIME_STEP_SEC ) {
$secret = self::base32_decode( $key );

switch ( $hash ) {
case 'sha1':
$secret = self::pad_secret( $secret, 20 );
break;
case 'sha256':
$secret = self::pad_secret( $secret, 32 );
break;
case 'sha512':
$secret = self::pad_secret( $secret, 64 );
break;
default:
throw new InvalidArgumentException( 'Invalid hash type specified!' );
}

if ( false === $step_count ) {
$step_count = floor( time() / $time_step );
}
Expand All @@ -271,7 +313,7 @@ public static function calc_totp( $key, $step_count = false, $digits = self::DEF

$hash = hash_hmac( $hash, $timestamp, $secret, true );

$offset = ord( $hash[19] ) & 0xf;
$offset = ord( $hash[ strlen( $hash ) - 1 ] ) & 0xf;

$code = (
( ( ord( $hash[ $offset + 0 ] ) & 0x7f ) << 24 ) |
Expand Down