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