working example page
[ga4php.git] / lib / lib.php
1 <?php
2
3 class GoogleAuthenticator {
4         
5         // first we init google authenticator by passing it a filename
6         // for its sqlite database.
7         function __construct($file) {
8                 if(file_exists($file)) {
9                         try {
10                                 $this->dbConnector = new PDO("sqlite:$file");
11                         } catch(PDOException $exep) {
12                                 $this->errorText = $exep->getMessage();
13                                 $this->dbConnector = false;
14                         }                       
15                 } else {
16                         $this->setupDB($file);
17                 }
18                 
19                 $this->dbFile = $file;
20         }
21         
22         // creates the database (tables);
23         function setupDB($file) {
24                 
25                 try {
26                         $this->dbConnector = new PDO("sqlite:$file");
27                 } catch(PDOException $exep) {
28                         $this->errorText = $exep->getMessage();
29                         $this->dbConnector = false;
30                 }                       
31         
32                 // here we create some tables and stuff
33                 $this->dbConnector->query('CREATE TABLE "users" ("user_id" INTEGER PRIMARY KEY AUTOINCREMENT,"user_name" TEXT NOT NULL,"user_tokenid" INTEGER)');
34                 $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)');
35         }
36         
37         // creates "user" in the database and returns a url for
38         // the phone. If user already exists, this returns false
39         // if any error occurs, this returns false
40         function setupUser($username) {
41                 $key = $this->createBase32Key();
42                 
43                 // sql for inserting into db
44                 $sql = "select * from users where user_name='$username'";
45                 $res = $this->dbConnector->query($sql);
46
47                 //if($res->fetchCount()>0) {
48                         //$this->errorText = "User Already Exists, $username";
49                         //return false;
50                 //}
51                 
52                 // and finally create 'em
53                 $hkey = $this->helperb322hex($key);
54                 error_log("key for user $username is $hkey, $key");
55                 $this->dbConnector->query("insert into tokens values (NULL, '$hkey', 'HOTP','0')");
56                 $id = $this->dbConnector->lastInsertID();
57                 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
58
59                 $url = $this->createURL($username, $key);
60                 
61                 return $url;
62         }
63         
64         // set the token type the user it going to use.
65         // this defaults to HOTP - we only do 30s token
66         // so lets not be able to set that yet
67         function setupTokenType($username, $tokentype) {
68                 if($tokentype!="HOTP" and $tokentype!="TOTP") {
69                         $errorText = "Invalid Token Type";
70                         return false;
71                 }
72                 
73                 $sql = "select * from users where user_name='$username'";
74                 $res = $this->dbConnector->query($sql);
75                 
76                 foreach($res as $row) {
77                         $tid = $row["user_tokenid"];    
78                 }
79                 
80                 
81                 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
82                 $sql = "update tokens set token_type='$tokentype' where token_id='$tid'";
83                 
84                 return true;    
85         }
86         
87         
88         // create "user" with insert
89         function createUser($username, $key) {
90                 // sql for inserting into db
91                 $sql = "select * from users where user_name='$username'";
92                 $res = $this->dbConnector->query($sql);
93
94                 //if($res) if($res->fetchCount()>0) {
95                         //$this->errorText = "User Already Exists, $username";
96                         //return false;
97                 //}
98                 
99                 // and finally create 'em
100                 $hkey = $this->helperb322hex($key);
101                 $this->dbConnector->query("insert into tokens values (NULL, '$hkey', 'HOTP', '0')");
102                 $id = $this->dbConnector->lastInsertID();
103                 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
104
105                 $url = $this->createURL($username, $key);
106                 
107                 return $url;
108         }
109         
110         // Replcate "user" in the database... All this really
111         // does is to replace the key for the user. Returns false
112         // if the user doesnt exist of the key is poop
113         function replaceUser($username) {
114                 $key = $this->createBase32Key();
115                 
116                 // delete the user - TODO, clean up auth tokens
117                 $sql = "delete from users where user_name='$username'";
118                 $res = $this->dbConnector->query($sql);
119                 
120                 // sql for inserting into db - just making sure.
121                 $sql = "select * from users where user_name='$username'";
122                 $res = $this->dbConnector->query($sql);
123
124                 if($res->fetchCount()>0) {
125                         $this->errorText = "User Already Exists, $username";
126                         return false;
127                 }
128                 
129                 // and finally create 'em
130                 $this->dbConnector->query("insert into tokens values (NULL, '$key', '0')");
131                 $id = $this->dbConnector->lastInsertID();
132                 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
133
134                 $url = $this->createURL($username, $key);
135                 
136                 return $url;
137         }
138         
139         // sets the key for a user - this is assuming you dont want
140         // to use one created by the application. returns false
141         // if the key is invalid or the user doesn't exist.
142         function setUserKey($username, $key) {
143                 $sql = "select * from users where user_name='$username'";
144                 $res = $this->dbConnector->query($sql);
145                 
146                 foreach($res as $row) {
147                         $tid = $row["user_tokenid"];    
148                 }
149                 
150                 
151                 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
152                 $sql = "update tokens set token_key='$key' where token_id='$tid'";
153                 
154                 return true;
155         }
156         
157         // self explanitory?
158         function deleteUser($username) {
159                 $sql = "select * from users where user_name='$username'";
160                 $res = $this->dbConnector->query($sql);
161                 
162                 foreach($res as $row) {
163                         $tid = $row["user_tokenid"];    
164                 }
165                 
166                 
167                 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
168                 $sql = "delete from tokens where token_id='$tid'";
169                 $this->dbConnector->query($sql);
170                 
171                 $sql = "delete from users where user_name='$username'";
172                 $this->dbConnector->query($sql);
173         }
174         
175         // user has input their user name and some code, authenticate
176         // it
177         function authenticateUser($username, $code) {
178                 $sql = "select * from users where user_name='$username'";
179                 $res = $this->dbConnector->query($sql);
180                 
181                 $tid = -1;
182                 foreach($res as $row) {
183                         $tid = $row["user_tokenid"];    
184                 }
185                 
186                 // for HOTP tokens we start at x and go to x+20
187                 
188                 // for TOTP we go +/-1min TODO = remember that +/- 1min should
189                 // be changed based on stepping if we change the expiration time
190                 // for keys
191                 
192                 //              $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)');
193                 
194                 $sql = "select * from tokens where token_id='$tid'";
195                 $res = $this->dbConnector->query($sql);
196                 
197                 $tkey = "";
198                 $ttype = "";
199                 $tlid = "";
200                 foreach($res as $row) {
201                         $tkey = $row["token_key"];
202                         $ttype = $row["token_type"];
203                         $tlid = $row["token_lastid"];   
204                 }
205                 
206                 switch($ttype) {
207                         case "HOTP":
208                                 $st = $tlid;
209                                 $en = $tlid+20;
210                                 for($i=$st; $i<$en; $i++) {
211                                         $stest = $this->oath_hotp($tkey, $i);
212                                         //echo "code: $code, $stest, $tkey\n";
213                                         if($code == $stest) {
214                                                 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
215                                                 $this->dbConnector->query($sql);
216                                                 return true;
217                                         }
218                                 }
219                                 return false;
220                                 break;
221                         case "TOTP":
222                                 break;
223                         default:
224                                 echo "how the frig did i end up here?";
225                 }
226                 
227                 return false;
228
229         }
230         
231         // this function allows a user to resync their key. If too
232         // many codes are called, we only check up to 20 codes in the future
233         // so if the user is at 21, they'll always fail. 
234         function resyncCode($username, $code1, $code2) {
235                         
236         }
237         
238         // gets the error text associated with the last error
239         function getErrorText() {
240                 return $this->errorText;
241         }
242         
243         // create a url compatibile with google authenticator.
244         function createURL($user, $key) {
245                 $url = "otpauth://hotp/$user?secret=$key";
246                 //echo "url: $url\n";
247                 return $url;
248         }
249         
250         // creeates a base 32 key (random)
251         function createBase32Key() {
252                 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
253                 $key = "";
254                 for($i=0; $i<16; $i++) {
255                         $offset = rand(0,strlen($alphabet)-1);
256                         //echo "$i off is $offset\n";
257                         $key .= $alphabet[$offset];
258                 }
259                 
260                 return $key;
261         }
262                 
263         
264         function helperb322hex($b32) {
265         $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
266
267         $out = "";
268         $dous = "";
269
270         for($i = 0; $i < strlen($b32); $i++) {
271                 $in = strrpos($alphabet, $b32[$i]);
272                 $b = str_pad(base_convert($in, 10, 2), 5, "0", STR_PAD_LEFT);
273             $out .= $b;
274             $dous .= $b.".";
275         }
276
277         $ar = str_split($out,20);
278
279         //echo "$dous, $b\n";
280
281         //print_r($ar);
282         $out2 = "";
283         foreach($ar as $val) {
284                 $rv = str_pad(base_convert($val, 2, 16), 5, "0", STR_PAD_LEFT);
285                 //echo "rv: $rv from $val\n";
286                 $out2 .= $rv;
287
288         }
289         //echo "$out2\n";
290
291         return $out2;
292         }
293         
294         function helperhex2b32($hex) {
295         $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
296
297         $ar = str_split($hex, 5);
298
299         $out = "";
300         foreach($ar as $var) {
301                 $bc = base_convert($var, 16, 2);
302                 $bin = str_pad($bc, 20, "0", STR_PAD_LEFT);
303                 $out .= $bin;
304                 //echo "$bc was, $var is, $bin are\n";
305         }
306
307         $out2 = "";
308         $ar2 = str_split($out, 5);
309         foreach($ar2 as $var2) {
310                 $bc = base_convert($var2, 2, 10);
311                 $out2 .= $alphabet[$bc];
312         }
313
314         return $out2;
315         }
316         
317         function oath_hotp($key, $counter)
318         {
319                 $key = pack("H*", $key);
320             $cur_counter = array(0,0,0,0,0,0,0,0);
321             for($i=7;$i>=0;$i--)
322             {
323                 $cur_counter[$i] = pack ('C*', $counter);
324                 $counter = $counter >> 8;
325             }
326             $bin_counter = implode($cur_counter);
327             // Pad to 8 chars
328             if (strlen ($bin_counter) < 8)
329             {
330                 $bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
331             }
332         
333             // HMAC
334             $hash = hash_hmac ('sha1', $bin_counter, $key);
335             return str_pad($this->oath_truncate($hash), 6, "0", STR_PAD_LEFT);
336         }
337         
338         function oath_truncate($hash, $length = 6)
339         {
340             // Convert to dec
341             foreach(str_split($hash,2) as $hex)
342             {
343                 $hmac_result[]=hexdec($hex);
344             }
345         
346             // Find offset
347             $offset = $hmac_result[19] & 0xf;
348         
349             // Algorithm from RFC
350             return
351             (
352                 (($hmac_result[$offset+0] & 0x7f) << 24 ) |
353                 (($hmac_result[$offset+1] & 0xff) << 16 ) |
354                 (($hmac_result[$offset+2] & 0xff) << 8 ) |
355                 ($hmac_result[$offset+3] & 0xff)
356             ) % pow(10,$length);
357         }
358         
359         
360         // some private data bits.
361         private $errorText;
362         private $dbFile;
363         private $dbConnector;
364 }
365 ?>