6 * Error checking, lots of error checking
9 class GoogleAuthenticator {
11 // first we init google authenticator by passing it a filename
12 // for its sqlite database.
13 function __construct($file) {
14 if(file_exists($file)) {
16 $this->dbConnector = new PDO("sqlite:$file");
17 } catch(PDOException $exep) {
18 $this->errorText = $exep->getMessage();
19 $this->dbConnector = false;
22 $this->setupDB($file);
25 $this->dbFile = $file;
28 // creates the database (tables);
29 function setupDB($file) {
32 $this->dbConnector = new PDO("sqlite:$file");
33 } catch(PDOException $exep) {
34 $this->errorText = $exep->getMessage();
35 $this->dbConnector = false;
38 // here we create some tables and stuff
39 $this->dbConnector->query('CREATE TABLE "users" ("user_id" INTEGER PRIMARY KEY AUTOINCREMENT,"user_name" TEXT NOT NULL,"user_tokenid" INTEGER)');
40 $this->dbConnector->query('CREATE TABLE "tokens" ("token_id" INTEGER PRIMARY KEY AUTOINCREMENT,"token_key" TEXT NOT NULL, "token_type" TEXT NOT NULL, "token_lastid" INTEGER NOT NULL)');
43 // creates "user" in the database and returns a url for
44 // the phone. If user already exists, this returns false
45 // if any error occurs, this returns false
46 function setupUser($username, $tokentype="HOTP") {
47 $key = $this->createBase32Key();
49 // sql for inserting into db
50 $key = $this->createUser($username, $key, $tokentype);
55 // this could get ugly for large databases.. we'll worry about that if it ever happens.
56 function getUserList() {
57 $res = $this->dbConnector->query("select user_name from users");
60 foreach($res as $row) {
61 //error_log("user: ".$row["user_name"]);
62 $ar[$i] = $row["user_name"];
69 // set the token type the user it going to use.
70 // this defaults to HOTP - we only do 30s token
71 // so lets not be able to set that yet
72 function setupTokenType($username, $tokentype) {
73 if($tokentype!="HOTP" and $tokentype!="TOTP") {
74 $errorText = "Invalid Token Type";
78 $sql = "select * from users where user_name='$username'";
79 $res = $this->dbConnector->query($sql);
81 foreach($res as $row) {
82 $tid = $row["user_tokenid"];
86 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
87 $sql = "update tokens set token_type='$tokentype' where token_id='$tid'";
93 // create "user" with insert
94 function createUser($username, $key, $ttype="HOTP") {
95 // sql for inserting into db
96 $sql = "select * from users where user_name='$username'";
97 $res = $this->dbConnector->query($sql);
99 //if($res) if($res->fetchCount()>0) {
100 //$this->errorText = "User Already Exists, $username";
104 // and finally create 'em
105 $hkey = $this->helperb322hex($key);
106 $this->dbConnector->query("insert into tokens values (NULL, '$hkey', '$ttype', '0')");
107 $id = $this->dbConnector->lastInsertID();
108 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
113 // Replcate "user" in the database... All this really
114 // does is to replace the key for the user. Returns false
115 // if the user doesnt exist of the key is poop
116 function replaceUser($username) {
117 $key = $this->createBase32Key();
119 // delete the user - TODO, clean up auth tokens
120 $sql = "delete from users where user_name='$username'";
121 $res = $this->dbConnector->query($sql);
123 // sql for inserting into db - just making sure.
124 $sql = "select * from users where user_name='$username'";
125 $res = $this->dbConnector->query($sql);
127 if($res->fetchCount()>0) {
128 $this->errorText = "User Already Exists, $username";
132 // and finally create 'em
133 $this->dbConnector->query("insert into tokens values (NULL, '$key', '0')");
134 $id = $this->dbConnector->lastInsertID();
135 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
137 $url = $this->createURL($username, $key);
142 // sets the key for a user - this is assuming you dont want
143 // to use one created by the application. returns false
144 // if the key is invalid or the user doesn't exist.
145 function setUserKey($username, $key) {
146 $sql = "select * from users where user_name='$username'";
147 $res = $this->dbConnector->query($sql);
149 foreach($res as $row) {
150 $tid = $row["user_tokenid"];
154 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
155 $sql = "update tokens set token_key='$key' where token_id='$tid'";
162 function userExists($username) {
163 $sql = "select * from users where user_name='$username'";
164 $res = $this->dbConnector->query($sql);
167 foreach($res as $row) {
168 $tid = $row["user_tokenid"];
171 if($tid == -1) return false;
177 function deleteUser($username) {
178 $sql = "select * from users where user_name='$username'";
179 $res = $this->dbConnector->query($sql);
181 foreach($res as $row) {
182 $tid = $row["user_tokenid"];
186 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
187 $sql = "delete from tokens where token_id='$tid'";
188 $this->dbConnector->query($sql);
190 $sql = "delete from users where user_name='$username'";
191 $this->dbConnector->query($sql);
194 // user has input their user name and some code, authenticate
196 function authenticateUser($username, $code) {
197 $sql = "select * from users where user_name='$username'";
198 $res = $this->dbConnector->query($sql);
201 foreach($res as $row) {
202 $tid = $row["user_tokenid"];
205 // for HOTP tokens we start at x and go to x+20
207 // for TOTP we go +/-1min TODO = remember that +/- 1min should
208 // be changed based on stepping if we change the expiration time
211 // $this->dbConnector->query('CREATE TABLE "tokens" ("token_id" INTEGER PRIMARY KEY AUTOINCREMENT,"token_key" TEXT NOT NULL, "token_type" TEXT NOT NULL, "token_lastid" INTEGER NOT NULL)');
213 $sql = "select * from tokens where token_id='$tid'";
214 $res = $this->dbConnector->query($sql);
219 foreach($res as $row) {
220 $tkey = $row["token_key"];
221 $ttype = $row["token_type"];
222 $tlid = $row["token_lastid"];
229 for($i=$st; $i<$en; $i++) {
230 $stest = $this->oath_hotp($tkey, $i);
231 //error_log("code: $code, $stest, $tkey, $tid");
232 if($code == $stest) {
233 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
234 $this->dbConnector->query($sql);
242 $t_ear = $t_now - 45;
243 $t_lat = $t_now + 60;
244 $t_st = ((int)($t_ear/30));
245 $t_en = ((int)($t_lat/30));
246 //error_log("kmac: $t_now, $t_ear, $t_lat, $t_st, $t_en");
247 for($i=$t_st; $i<=$t_en; $i++) {
248 $stest = $this->oath_hotp($tkey, $i);
249 //error_log("code: $code, $stest, $tkey\n");
250 if($code == $stest) {
256 echo "how the frig did i end up here?";
263 // this function allows a user to resync their key. If too
264 // many codes are called, we only check up to 20 codes in the future
265 // so if the user is at 21, they'll always fail.
266 function resyncCode($username, $code1, $code2) {
267 // here we'll go from 0 all the way thru to 200k.. if we cant find the code, so be it, they'll need a new one
268 $sql = "select * from users where user_name='$username'";
269 $res = $this->dbConnector->query($sql);
272 foreach($res as $row) {
273 $tid = $row["user_tokenid"];
276 // for HOTP tokens we start at x and go to x+20
278 // for TOTP we go +/-1min TODO = remember that +/- 1min should
279 // be changed based on stepping if we change the expiration time
282 // $this->dbConnector->query('CREATE TABLE "tokens" ("token_id" INTEGER PRIMARY KEY AUTOINCREMENT,"token_key" TEXT NOT NULL, "token_type" TEXT NOT NULL, "token_lastid" INTEGER NOT NULL)');
284 $sql = "select * from tokens where token_id='$tid'";
285 $res = $this->dbConnector->query($sql);
290 foreach($res as $row) {
291 $tkey = $row["token_key"];
292 $ttype = $row["token_type"];
293 $tlid = $row["token_lastid"];
300 for($i=$st; $i<$en; $i++) {
301 $stest = $this->oath_hotp($tkey, $i);
302 //echo "code: $code, $stest, $tkey\n";
303 if($code1 == $stest) {
304 $stest2 = $this->oath_hotp($tkey, $i+1);
305 if($code2 == $stest2) {
306 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
307 $this->dbConnector->query($sql);
317 echo "how the frig did i end up here?";
324 // gets the error text associated with the last error
325 function getErrorText() {
326 return $this->errorText;
329 // create a url compatibile with google authenticator.
330 function createURL($user, $key,$toktype = "HOTP") {
331 // oddity in the google authenticator... hotp needs to be lowercase.
332 $toktype = strtolower($toktype);
333 if($toktype == "hotp") {
334 $url = "otpauth://$toktype/$user?secret=$key&counter=1";
336 $url = "otpauth://$toktype/$user?secret=$key";
338 //echo "url: $url\n";
342 // creeates a base 32 key (random)
343 function createBase32Key() {
344 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
346 for($i=0; $i<16; $i++) {
347 $offset = rand(0,strlen($alphabet)-1);
348 //echo "$i off is $offset\n";
349 $key .= $alphabet[$offset];
356 function helperb322hex($b32) {
357 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
362 for($i = 0; $i < strlen($b32); $i++) {
363 $in = strrpos($alphabet, $b32[$i]);
364 $b = str_pad(base_convert($in, 10, 2), 5, "0", STR_PAD_LEFT);
369 $ar = str_split($out,20);
371 //echo "$dous, $b\n";
375 foreach($ar as $val) {
376 $rv = str_pad(base_convert($val, 2, 16), 5, "0", STR_PAD_LEFT);
377 //echo "rv: $rv from $val\n";
386 function helperhex2b32($hex) {
387 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
389 $ar = str_split($hex, 5);
392 foreach($ar as $var) {
393 $bc = base_convert($var, 16, 2);
394 $bin = str_pad($bc, 20, "0", STR_PAD_LEFT);
396 //echo "$bc was, $var is, $bin are\n";
400 $ar2 = str_split($out, 5);
401 foreach($ar2 as $var2) {
402 $bc = base_convert($var2, 2, 10);
403 $out2 .= $alphabet[$bc];
409 function oath_hotp($key, $counter)
411 $key = pack("H*", $key);
412 $cur_counter = array(0,0,0,0,0,0,0,0);
415 $cur_counter[$i] = pack ('C*', $counter);
416 $counter = $counter >> 8;
418 $bin_counter = implode($cur_counter);
420 if (strlen ($bin_counter) < 8)
422 $bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
426 $hash = hash_hmac ('sha1', $bin_counter, $key);
427 return str_pad($this->oath_truncate($hash), 6, "0", STR_PAD_LEFT);
430 function oath_truncate($hash, $length = 6)
433 foreach(str_split($hash,2) as $hex)
435 $hmac_result[]=hexdec($hex);
439 $offset = $hmac_result[19] & 0xf;
441 // Algorithm from RFC
444 (($hmac_result[$offset+0] & 0x7f) << 24 ) |
445 (($hmac_result[$offset+1] & 0xff) << 16 ) |
446 (($hmac_result[$offset+2] & 0xff) << 8 ) |
447 ($hmac_result[$offset+3] & 0xff)
452 // some private data bits.
455 private $dbConnector;