diff --git a/demo/toopher_demo.php b/demo/toopher_demo.php index 367c5a4..ed247d6 100644 --- a/demo/toopher_demo.php +++ b/demo/toopher_demo.php @@ -28,6 +28,7 @@ $key = getenv('TOOPHER_CONSUMER_KEY'); $secret = getenv('TOOPHER_CONSUMER_SECRET'); +$url = getenv('TOOPHER_BASE_URL'); if(empty($key) || empty($secret)){ echo("enter consumer credentials (set environment variables to prevent prompting):\n"); echo("TOOPHER_CONSUMER_KEY="); @@ -37,7 +38,7 @@ } echo ("using key=$key, secret=$secret\n"); -$toopher = new ToopherAPI($key, $secret); +$toopher = new ToopherAPI($key, $secret, $url); echo("\nSTEP 1: Pair device\n"); echo("enter pairing phrase:"); @@ -46,6 +47,7 @@ $userName = rtrim(fgets($stdin)); $pairing = $toopher->pair($phrase, $userName); +$pairing_secret = $pairing['secret']; while(!$pairing['enabled']){ echo("waiting for authorization...\n"); @@ -60,14 +62,22 @@ echo("enter action name, or [ENTER] for none:"); while(true){ $action = rtrim(fgets($stdin)); - echo("sending authentication request...\n"); - $auth = $toopher->authenticate($pairing['id'], $terminalName, $action); - while($auth['pending']){ - echo("waiting for authentication...\n"); - sleep(1); - $auth = $toopher->getAuthenticationStatus($auth['id']); + echo("enter OTP for local validation, or [ENTER] to validate using Toopher API:"); + $otp = rtrim(fgets($stdin)); + if (strlen($otp) > 0) { + echo("checking submitted OTP...\n"); + list ($otpValid, $otpDrift) = ToopherAPI::verifyOtp($pairing_secret, $otp); + echo("OTP check = " . ($otpValid ? "valid with drift=$otpDrift" : "invalid") . ". Enter another action to authorize again, or [Ctrl+C] to exit:"); + } else { + echo("sending authentication request...\n"); + $auth = $toopher->authenticate($pairing['id'], $terminalName, $action); + while($auth['pending']){ + echo("waiting for authentication...\n"); + sleep(1); + $auth = $toopher->getAuthenticationStatus($auth['id']); + } + + echo("Successfully authorized action '$action'. Enter another action to authorize again, or [Ctrl+C] to exit:"); } - - echo("Successfully authorized action '$action'. Enter another action to authorize again, or [Ctrl+C] to exit:"); } ?> diff --git a/lib/toopher_api.php b/lib/toopher_api.php index 07511cc..20ae41f 100644 --- a/lib/toopher_api.php +++ b/lib/toopher_api.php @@ -85,6 +85,7 @@ private function makePairResponse($result) return array( 'id' => $result['id'], 'enabled' => $result['enabled'], + 'secret' => $result['secret'], 'userId' => $result['user']['id'], 'userName' => $result['user']['name'] ); @@ -138,6 +139,49 @@ private function request($method, $endpoint, $parameters = []) } return $decoded; } + + + /////////////////////////////////////////////////////////////// + // OTP VALIDATION + /////////////////////////////////////////////////////////////// + public static function oath_hotp($seed, $counter, $otpLength) + { + $bin_counter = pack('N*', 0) . pack('N*', $counter); // Counter must be 64-bit int + $hash = hash_hmac ('sha1', $bin_counter, $seed, true); + + return str_pad(self::oathTruncate($hash, $otpLength), $otpLength, '0', STR_PAD_LEFT); + } + + public static function verifyOtp($utf8seed, $otp, $drift=0, $windowSize = 2, $otpResolutionSeconds = 30, $otpLength = 6) { + + $timeStamp = floor(microtime(true)/$otpResolutionSeconds); + + $binarySeed = utf8_decode($utf8seed); + + for ($i = $drift - $windowSize; $i <= $drift + $windowSize; $i++){ + $ts = $timeStamp + $i; + if (self::oath_hotp($binarySeed, $ts, $otpLength) == $otp) + return array(true, $i); + } + + return array (false, 0); + + } + + public static function oathTruncate($hash, $otpLength) + { + $offset = ord($hash[19]) & 0xf; + + return ( + ((ord($hash[$offset+0]) & 0x7f) << 24 ) | + ((ord($hash[$offset+1]) & 0xff) << 16 ) | + ((ord($hash[$offset+2]) & 0xff) << 8 ) | + (ord($hash[$offset+3]) & 0xff) + ) % pow(10, $otpLength); + } + + + } ?>