Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 19 additions & 9 deletions demo/toopher_demo.php
Original file line number Diff line number Diff line change
Expand Up @@ -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=");
Expand All @@ -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:");
Expand All @@ -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");
Expand All @@ -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:");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording here tripped me up a bit--I wasn't sure what my options were. Perhaps we could reword to something like this:

Press [ENTER] to validate using the Toopher API or enter the OTP generated by the Toopher app:

$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:");
}
?>
44 changes: 44 additions & 0 deletions lib/toopher_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']
);
Expand Down Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do? I see a lot of bit manipulation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best explanation is http://en.wikipedia.org/wiki/HMAC-based_One-time_Password_Algorithm. Truncate is:

a function that selects 4 bytes from the result of the HMAC in a defined manner

This implementation is just a php port of the truncate algorithm from the oath reference implementation

{
$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);
}



}

?>