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) {
47 $key = $this->createBase32Key();
49 // sql for inserting into db
50 $sql = "select * from users where user_name='$username'";
51 $res = $this->dbConnector->query($sql);
53 //if($res->fetchCount()>0) {
54 //$this->errorText = "User Already Exists, $username";
58 // and finally create 'em
59 $hkey = $this->helperb322hex($key);
60 error_log("key for user $username is $hkey, $key");
61 $this->dbConnector->query("insert into tokens values (NULL, '$hkey', 'HOTP','0')");
62 $id = $this->dbConnector->lastInsertID();
63 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
65 $url = $this->createURL($username, $key);
71 // this could get ugly for large databases.. we'll worry about that if it ever happens.
72 function getUserList() {
73 $res = $this->dbConnector->query("select user_name from users");
76 foreach($res as $row) {
77 error_log("user: ".$row["user_name"]);
78 $ar[$i] = $row["user_name"];
85 // set the token type the user it going to use.
86 // this defaults to HOTP - we only do 30s token
87 // so lets not be able to set that yet
88 function setupTokenType($username, $tokentype) {
89 if($tokentype!="HOTP" and $tokentype!="TOTP") {
90 $errorText = "Invalid Token Type";
94 $sql = "select * from users where user_name='$username'";
95 $res = $this->dbConnector->query($sql);
97 foreach($res as $row) {
98 $tid = $row["user_tokenid"];
102 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
103 $sql = "update tokens set token_type='$tokentype' where token_id='$tid'";
109 // create "user" with insert
110 function createUser($username, $key) {
111 // sql for inserting into db
112 $sql = "select * from users where user_name='$username'";
113 $res = $this->dbConnector->query($sql);
115 //if($res) if($res->fetchCount()>0) {
116 //$this->errorText = "User Already Exists, $username";
120 // and finally create 'em
121 $hkey = $this->helperb322hex($key);
122 $this->dbConnector->query("insert into tokens values (NULL, '$hkey', 'HOTP', '0')");
123 $id = $this->dbConnector->lastInsertID();
124 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
126 $url = $this->createURL($username, $key);
131 // Replcate "user" in the database... All this really
132 // does is to replace the key for the user. Returns false
133 // if the user doesnt exist of the key is poop
134 function replaceUser($username) {
135 $key = $this->createBase32Key();
137 // delete the user - TODO, clean up auth tokens
138 $sql = "delete from users where user_name='$username'";
139 $res = $this->dbConnector->query($sql);
141 // sql for inserting into db - just making sure.
142 $sql = "select * from users where user_name='$username'";
143 $res = $this->dbConnector->query($sql);
145 if($res->fetchCount()>0) {
146 $this->errorText = "User Already Exists, $username";
150 // and finally create 'em
151 $this->dbConnector->query("insert into tokens values (NULL, '$key', '0')");
152 $id = $this->dbConnector->lastInsertID();
153 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
155 $url = $this->createURL($username, $key);
160 // sets the key for a user - this is assuming you dont want
161 // to use one created by the application. returns false
162 // if the key is invalid or the user doesn't exist.
163 function setUserKey($username, $key) {
164 $sql = "select * from users where user_name='$username'";
165 $res = $this->dbConnector->query($sql);
167 foreach($res as $row) {
168 $tid = $row["user_tokenid"];
172 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
173 $sql = "update tokens set token_key='$key' where token_id='$tid'";
180 function userExists($username) {
181 $sql = "select * from users where user_name='$username'";
182 $res = $this->dbConnector->query($sql);
185 foreach($res as $row) {
186 $tid = $row["user_tokenid"];
189 if($tid == -1) return false;
195 function deleteUser($username) {
196 $sql = "select * from users where user_name='$username'";
197 $res = $this->dbConnector->query($sql);
199 foreach($res as $row) {
200 $tid = $row["user_tokenid"];
204 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
205 $sql = "delete from tokens where token_id='$tid'";
206 $this->dbConnector->query($sql);
208 $sql = "delete from users where user_name='$username'";
209 $this->dbConnector->query($sql);
212 // user has input their user name and some code, authenticate
214 function authenticateUser($username, $code) {
215 $sql = "select * from users where user_name='$username'";
216 $res = $this->dbConnector->query($sql);
219 foreach($res as $row) {
220 $tid = $row["user_tokenid"];
223 // for HOTP tokens we start at x and go to x+20
225 // for TOTP we go +/-1min TODO = remember that +/- 1min should
226 // be changed based on stepping if we change the expiration time
229 // $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)');
231 $sql = "select * from tokens where token_id='$tid'";
232 $res = $this->dbConnector->query($sql);
237 foreach($res as $row) {
238 $tkey = $row["token_key"];
239 $ttype = $row["token_type"];
240 $tlid = $row["token_lastid"];
247 for($i=$st; $i<$en; $i++) {
248 $stest = $this->oath_hotp($tkey, $i);
249 //echo "code: $code, $stest, $tkey\n";
250 if($code == $stest) {
251 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
252 $this->dbConnector->query($sql);
261 echo "how the frig did i end up here?";
268 // this function allows a user to resync their key. If too
269 // many codes are called, we only check up to 20 codes in the future
270 // so if the user is at 21, they'll always fail.
271 function resyncCode($username, $code1, $code2) {
272 // 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
273 $sql = "select * from users where user_name='$username'";
274 $res = $this->dbConnector->query($sql);
277 foreach($res as $row) {
278 $tid = $row["user_tokenid"];
281 // for HOTP tokens we start at x and go to x+20
283 // for TOTP we go +/-1min TODO = remember that +/- 1min should
284 // be changed based on stepping if we change the expiration time
287 // $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)');
289 $sql = "select * from tokens where token_id='$tid'";
290 $res = $this->dbConnector->query($sql);
295 foreach($res as $row) {
296 $tkey = $row["token_key"];
297 $ttype = $row["token_type"];
298 $tlid = $row["token_lastid"];
305 for($i=$st; $i<$en; $i++) {
306 $stest = $this->oath_hotp($tkey, $i);
307 //echo "code: $code, $stest, $tkey\n";
308 if($code1 == $stest) {
309 $stest2 = $this->oath_hotp($tkey, $i+1);
310 if($code2 == $stest2) {
311 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
312 $this->dbConnector->query($sql);
322 echo "how the frig did i end up here?";
329 // gets the error text associated with the last error
330 function getErrorText() {
331 return $this->errorText;
334 // create a url compatibile with google authenticator.
335 function createURL($user, $key) {
336 $url = "otpauth://hotp/$user?secret=$key";
337 //echo "url: $url\n";
341 // creeates a base 32 key (random)
342 function createBase32Key() {
343 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
345 for($i=0; $i<16; $i++) {
346 $offset = rand(0,strlen($alphabet)-1);
347 //echo "$i off is $offset\n";
348 $key .= $alphabet[$offset];
355 function helperb322hex($b32) {
356 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
361 for($i = 0; $i < strlen($b32); $i++) {
362 $in = strrpos($alphabet, $b32[$i]);
363 $b = str_pad(base_convert($in, 10, 2), 5, "0", STR_PAD_LEFT);
368 $ar = str_split($out,20);
370 //echo "$dous, $b\n";
374 foreach($ar as $val) {
375 $rv = str_pad(base_convert($val, 2, 16), 5, "0", STR_PAD_LEFT);
376 //echo "rv: $rv from $val\n";
385 function helperhex2b32($hex) {
386 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
388 $ar = str_split($hex, 5);
391 foreach($ar as $var) {
392 $bc = base_convert($var, 16, 2);
393 $bin = str_pad($bc, 20, "0", STR_PAD_LEFT);
395 //echo "$bc was, $var is, $bin are\n";
399 $ar2 = str_split($out, 5);
400 foreach($ar2 as $var2) {
401 $bc = base_convert($var2, 2, 10);
402 $out2 .= $alphabet[$bc];
408 function oath_hotp($key, $counter)
410 $key = pack("H*", $key);
411 $cur_counter = array(0,0,0,0,0,0,0,0);
414 $cur_counter[$i] = pack ('C*', $counter);
415 $counter = $counter >> 8;
417 $bin_counter = implode($cur_counter);
419 if (strlen ($bin_counter) < 8)
421 $bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
425 $hash = hash_hmac ('sha1', $bin_counter, $key);
426 return str_pad($this->oath_truncate($hash), 6, "0", STR_PAD_LEFT);
429 function oath_truncate($hash, $length = 6)
432 foreach(str_split($hash,2) as $hex)
434 $hmac_result[]=hexdec($hex);
438 $offset = $hmac_result[19] & 0xf;
440 // Algorithm from RFC
443 (($hmac_result[$offset+0] & 0x7f) << 24 ) |
444 (($hmac_result[$offset+1] & 0xff) << 16 ) |
445 (($hmac_result[$offset+2] & 0xff) << 8 ) |
446 ($hmac_result[$offset+3] & 0xff)
451 // some private data bits.
454 private $dbConnector;