Browse Source

Upload files to 'classes'

Stortebeker 6 years ago
parent
commit
54e58493ab
5 changed files with 1277 additions and 1211 deletions
  1. 590
    563
      classes/torrentsearch.class.php
  2. 174
    168
      classes/tracker.class.php
  3. 124
    113
      classes/twofa.class.php
  4. 345
    327
      classes/u2f.class.php
  5. 44
    40
      classes/useragent.class.php

+ 590
- 563
classes/torrentsearch.class.php
File diff suppressed because it is too large
View File


+ 174
- 168
classes/tracker.class.php View File

1
-<?
1
+<?php
2
 // TODO: Turn this into a class with nice functions like update_user, delete_torrent, etc.
2
 // TODO: Turn this into a class with nice functions like update_user, delete_torrent, etc.
3
-class Tracker {
4
-  const STATS_MAIN = 0;
5
-  const STATS_USER = 1;
3
+class Tracker
4
+{
5
+    const STATS_MAIN = 0;
6
+    const STATS_USER = 1;
6
 
7
 
7
-  public static $Requests = [];
8
+    public static $Requests = [];
8
 
9
 
9
-  /**
10
-   * Send a GET request over a socket directly to the tracker
11
-   * For example, Tracker::update_tracker('change_passkey', array('oldpasskey' => OLD_PASSKEY, 'newpasskey' => NEW_PASSKEY)) will send the request:
12
-   * GET /tracker_32_char_secret_code/update?action=change_passkey&oldpasskey=OLD_PASSKEY&newpasskey=NEW_PASSKEY HTTP/1.1
13
-   *
14
-   * @param string $Action The action to send
15
-   * @param array $Updates An associative array of key->value pairs to send to the tracker
16
-   * @param boolean $ToIRC Sends a message to the channel #tracker with the GET URL.
17
-   */
18
-  public static function update_tracker($Action, $Updates, $ToIRC = false) {
19
-    // Build request
20
-    $Get = TRACKER_SECRET . "/update?action=$Action";
21
-    foreach ($Updates as $Key => $Value) {
22
-      $Get .= "&$Key=$Value";
23
-    }
10
+    /**
11
+     * Send a GET request over a socket directly to the tracker
12
+     * For example, Tracker::update_tracker('change_passkey', array('oldpasskey' => OLD_PASSKEY, 'newpasskey' => NEW_PASSKEY)) will send the request:
13
+     * GET /tracker_32_char_secret_code/update?action=change_passkey&oldpasskey=OLD_PASSKEY&newpasskey=NEW_PASSKEY HTTP/1.1
14
+     *
15
+     * @param string $Action The action to send
16
+     * @param array $Updates An associative array of key->value pairs to send to the tracker
17
+     * @param boolean $ToIRC Sends a message to the channel #tracker with the GET URL.
18
+     */
19
+    public static function update_tracker($Action, $Updates, $ToIRC = false)
20
+    {
21
+        // Build request
22
+        $Get = TRACKER_SECRET . "/update?action=$Action";
23
+        foreach ($Updates as $Key => $Value) {
24
+            $Get .= "&$Key=$Value";
25
+        }
24
 
26
 
25
-    $MaxAttempts = 3;
26
-    // don't wait around if we're debugging
27
-    if (DEBUG_MODE) {
28
-      $MaxAttempts = 1;
29
-    }
27
+        $MaxAttempts = 3;
28
+        // don't wait around if we're debugging
29
+        if (DEBUG_MODE) {
30
+            $MaxAttempts = 1;
31
+        }
30
 
32
 
31
-    $Err = false;
32
-    if (self::send_request($Get, $MaxAttempts, $Err) === false) {
33
-      send_irc("PRIVMSG " . BOT_DEBUG_CHAN . " :$MaxAttempts $Err $Get");
34
-      if (G::$Cache->get_value('ocelot_error_reported') === false) {
35
-        send_irc('PRIVMSG ' . ADMIN_CHAN . " :Failed to update ocelot: $Err : $Get");
36
-        G::$Cache->cache_value('ocelot_error_reported', true, 3600);
37
-      }
38
-      return false;
33
+        $Err = false;
34
+        if (self::send_request($Get, $MaxAttempts, $Err) === false) {
35
+            send_irc("PRIVMSG " . BOT_DEBUG_CHAN . " :$MaxAttempts $Err $Get");
36
+            if (G::$Cache->get_value('ocelot_error_reported') === false) {
37
+                send_irc('PRIVMSG ' . ADMIN_CHAN . " :Failed to update ocelot: $Err : $Get");
38
+                G::$Cache->cache_value('ocelot_error_reported', true, 3600);
39
+            }
40
+            return false;
41
+        }
42
+        return true;
39
     }
43
     }
40
-    return true;
41
-  }
42
 
44
 
43
-  /**
44
-   * Get global peer stats from the tracker
45
-   *
46
-   * @return array(0 => $Leeching, 1 => $Seeding) or false if request failed
47
-   */
48
-  public static function global_peer_count() {
49
-    $Stats = self::get_stats(self::STATS_MAIN);
50
-    if (isset($Stats['leechers tracked']) && isset($Stats['seeders tracked'])) {
51
-      $Leechers = $Stats['leechers tracked'];
52
-      $Seeders = $Stats['seeders tracked'];
53
-    } else {
54
-      return false;
45
+    /**
46
+     * Get global peer stats from the tracker
47
+     *
48
+     * @return array(0 => $Leeching, 1 => $Seeding) or false if request failed
49
+     */
50
+    public static function global_peer_count()
51
+    {
52
+        $Stats = self::get_stats(self::STATS_MAIN);
53
+        if (isset($Stats['leechers tracked']) && isset($Stats['seeders tracked'])) {
54
+            $Leechers = $Stats['leechers tracked'];
55
+            $Seeders = $Stats['seeders tracked'];
56
+        } else {
57
+            return false;
58
+        }
59
+        return array($Leechers, $Seeders);
55
     }
60
     }
56
-    return array($Leechers, $Seeders);
57
-  }
58
 
61
 
59
-  /**
60
-   * Get peer stats for a user from the tracker
61
-   *
62
-   * @param string $TorrentPass The user's pass key
63
-   * @return array(0 => $Leeching, 1 => $Seeding) or false if the request failed
64
-   */
65
-  public static function user_peer_count($TorrentPass) {
66
-    $Stats = self::get_stats(self::STATS_USER, array('key' => $TorrentPass));
67
-    if ($Stats === false) {
68
-      return false;
69
-    }
70
-    if (isset($Stats['leeching']) && isset($Stats['seeding'])) {
71
-      $Leeching = $Stats['leeching'];
72
-      $Seeding = $Stats['seeding'];
73
-    } else {
74
-      // User doesn't exist, but don't tell anyone
75
-      $Leeching = $Seeding = 0;
62
+    /**
63
+     * Get peer stats for a user from the tracker
64
+     *
65
+     * @param string $TorrentPass The user's pass key
66
+     * @return array(0 => $Leeching, 1 => $Seeding) or false if the request failed
67
+     */
68
+    public static function user_peer_count($TorrentPass)
69
+    {
70
+        $Stats = self::get_stats(self::STATS_USER, array('key' => $TorrentPass));
71
+        if ($Stats === false) {
72
+            return false;
73
+        }
74
+        if (isset($Stats['leeching']) && isset($Stats['seeding'])) {
75
+            $Leeching = $Stats['leeching'];
76
+            $Seeding = $Stats['seeding'];
77
+        } else {
78
+            // User doesn't exist, but don't tell anyone
79
+            $Leeching = $Seeding = 0;
80
+        }
81
+        return array($Leeching, $Seeding);
76
     }
82
     }
77
-    return array($Leeching, $Seeding);
78
-  }
79
 
83
 
80
-  /**
81
-   * Get whatever info the tracker has to report
82
-   *
83
-   * @return results from get_stats()
84
-   */
85
-  public static function info() {
86
-    return self::get_stats(self::STATS_MAIN);
87
-  }
88
-
89
-  /**
90
-   * Send a stats request to the tracker and process the results
91
-   *
92
-   * @param int $Type Stats type to get
93
-   * @param array $Params Parameters required by stats type
94
-   * @return array with stats in named keys or false if the request failed
95
-   */
96
-  private static function get_stats($Type, $Params = false) {
97
-    if (!defined('TRACKER_REPORTKEY')) {
98
-      return false;
99
-    }
100
-    $Get = TRACKER_REPORTKEY . '/report?';
101
-    if ($Type === self::STATS_MAIN) {
102
-      $Get .= 'get=stats';
103
-    } elseif ($Type === self::STATS_USER && !empty($Params['key'])) {
104
-      $Get .= "get=user&key=$Params[key]";
105
-    } else {
106
-      return false;
84
+    /**
85
+     * Get whatever info the tracker has to report
86
+     *
87
+     * @return results from get_stats()
88
+     */
89
+    public static function info()
90
+    {
91
+        return self::get_stats(self::STATS_MAIN);
107
     }
92
     }
108
-    $Response = self::send_request($Get);
109
-    if ($Response === false) {
110
-      return false;
111
-    }
112
-    $Stats = [];
113
-    foreach (explode("\n", $Response) as $Stat) {
114
-      list($Val, $Key) = explode(" ", $Stat, 2);
115
-      $Stats[$Key] = $Val;
93
+
94
+    /**
95
+     * Send a stats request to the tracker and process the results
96
+     *
97
+     * @param int $Type Stats type to get
98
+     * @param array $Params Parameters required by stats type
99
+     * @return array with stats in named keys or false if the request failed
100
+     */
101
+    private static function get_stats($Type, $Params = false)
102
+    {
103
+        if (!defined('TRACKER_REPORTKEY')) {
104
+            return false;
105
+        }
106
+        $Get = TRACKER_REPORTKEY . '/report?';
107
+        if ($Type === self::STATS_MAIN) {
108
+            $Get .= 'get=stats';
109
+        } elseif ($Type === self::STATS_USER && !empty($Params['key'])) {
110
+            $Get .= "get=user&key=$Params[key]";
111
+        } else {
112
+            return false;
113
+        }
114
+        $Response = self::send_request($Get);
115
+        if ($Response === false) {
116
+            return false;
117
+        }
118
+        $Stats = [];
119
+        foreach (explode("\n", $Response) as $Stat) {
120
+            list($Val, $Key) = explode(" ", $Stat, 2);
121
+            $Stats[$Key] = $Val;
122
+        }
123
+        return $Stats;
116
     }
124
     }
117
-    return $Stats;
118
-  }
119
 
125
 
120
-  /**
121
-   * Send a request to the tracker
122
-   *
123
-   * @param string $Path GET string to send to the tracker
124
-   * @param int $MaxAttempts Maximum number of failed attempts before giving up
125
-   * @param $Err Variable to use as storage for the error string if the request fails
126
-   * @return tracker response message or false if the request failed
127
-   */
128
-  private static function send_request($Get, $MaxAttempts = 1, &$Err = false) {
129
-    $Header = "GET /$Get HTTP/1.1\r\nConnection: Close\r\n\r\n";
130
-    $Attempts = 0;
131
-    $Sleep = 0;
132
-    $Success = false;
133
-    $StartTime = microtime(true);
134
-    while (!$Success && $Attempts++ < $MaxAttempts) {
135
-      if ($Sleep) {
136
-        sleep($Sleep);
137
-      }
126
+    /**
127
+     * Send a request to the tracker
128
+     *
129
+     * @param string $Path GET string to send to the tracker
130
+     * @param int $MaxAttempts Maximum number of failed attempts before giving up
131
+     * @param $Err Variable to use as storage for the error string if the request fails
132
+     * @return tracker response message or false if the request failed
133
+     */
134
+    private static function send_request($Get, $MaxAttempts = 1, &$Err = false)
135
+    {
136
+        $Header = "GET /$Get HTTP/1.1\r\nConnection: Close\r\n\r\n";
137
+        $Attempts = 0;
138
+        $Sleep = 0;
139
+        $Success = false;
140
+        $StartTime = microtime(true);
141
+        while (!$Success && $Attempts++ < $MaxAttempts) {
142
+            if ($Sleep) {
143
+                sleep($Sleep);
144
+            }
138
 
145
 
139
-      // spend some time retrying if we're not in DEBUG_MODE
140
-      if (!DEBUG_MODE) {
141
-        $Sleep = 6;
142
-      }
146
+            // spend some time retrying if we're not in DEBUG_MODE
147
+            if (!DEBUG_MODE) {
148
+                $Sleep = 6;
149
+            }
143
 
150
 
144
-      // Send request
145
-      $File = fsockopen(TRACKER_HOST, TRACKER_PORT, $ErrorNum, $ErrorString);
146
-      if ($File) {
147
-        if (fwrite($File, $Header) === false) {
148
-          $Err = "Failed to fwrite()";
149
-          $Sleep = 3;
150
-          continue;
151
-        }
152
-      } else {
153
-        $Err = "Failed to fsockopen() - $ErrorNum - $ErrorString";
154
-        continue;
155
-      }
151
+            // Send request
152
+            $File = fsockopen(TRACKER_HOST, TRACKER_PORT, $ErrorNum, $ErrorString);
153
+            if ($File) {
154
+                if (fwrite($File, $Header) === false) {
155
+                    $Err = "Failed to fwrite()";
156
+                    $Sleep = 3;
157
+                    continue;
158
+                }
159
+            } else {
160
+                $Err = "Failed to fsockopen() - $ErrorNum - $ErrorString";
161
+                continue;
162
+            }
156
 
163
 
157
-      // Check for response.
158
-      $Response = '';
159
-      while (!feof($File)) {
160
-        $Response .= fread($File, 1024);
161
-      }
162
-      $DataStart = strpos($Response, "\r\n\r\n") + 4;
163
-      $DataEnd = strrpos($Response, "\n");
164
-      if ($DataEnd > $DataStart) {
165
-        $Data = substr($Response, $DataStart, $DataEnd - $DataStart);
166
-      } else {
167
-        $Data = "";
168
-      }
169
-      $Status = substr($Response, $DataEnd + 1);
170
-      if ($Status == "success") {
171
-        $Success = true;
172
-      }
173
-    }
174
-    $Request = array(
175
-      'path' => substr($Get, strpos($Get, '/')),
176
-      'response' => ($Success ? $Data : $Response),
177
-      'status' => ($Success ? 'ok' : 'failed'),
178
-      'time' => 1000 * (microtime(true) - $StartTime)
179
-    );
180
-    self::$Requests[] = $Request;
181
-    if ($Success) {
182
-      return $Data;
164
+            // Check for response.
165
+            $Response = '';
166
+            while (!feof($File)) {
167
+                $Response .= fread($File, 1024);
168
+            }
169
+            $DataStart = strpos($Response, "\r\n\r\n") + 4;
170
+            $DataEnd = strrpos($Response, "\n");
171
+            if ($DataEnd > $DataStart) {
172
+                $Data = substr($Response, $DataStart, $DataEnd - $DataStart);
173
+            } else {
174
+                $Data = "";
175
+            }
176
+            $Status = substr($Response, $DataEnd + 1);
177
+            if ($Status == "success") {
178
+                $Success = true;
179
+            }
180
+        }
181
+        $Request = array(
182
+        'path' => substr($Get, strpos($Get, '/')),
183
+        'response' => ($Success ? $Data : $Response),
184
+        'status' => ($Success ? 'ok' : 'failed'),
185
+        'time' => 1000 * (microtime(true) - $StartTime)
186
+        );
187
+        self::$Requests[] = $Request;
188
+        if ($Success) {
189
+            return $Data;
190
+        }
191
+        return false;
183
     }
192
     }
184
-    return false;
185
-  }
186
 }
193
 }
187
-?>

+ 124
- 113
classes/twofa.class.php View File

1
 <?php
1
 <?php
2
 
2
 
3
-class TwoFactorAuth {
4
-  private $algorithm;
5
-  private $period;
6
-  private $digits;
7
-  private $issuer;
8
-  private static $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
9
-  private static $_base32;
10
-  private static $_base32lookup = [];
11
-  private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
12
-
13
-  function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1') {
14
-    $this->issuer = $issuer;
15
-    $this->digits = $digits;
16
-    $this->period = $period;
17
-
18
-    $algorithm = strtolower(trim($algorithm));
19
-    if (!in_array($algorithm, self::$_supportedalgos)){
20
-      $algorithm = 'sha1';
3
+class TwoFactorAuth
4
+{
5
+    private $algorithm;
6
+    private $period;
7
+    private $digits;
8
+    private $issuer;
9
+    private static $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
10
+    private static $_base32;
11
+    private static $_base32lookup = [];
12
+    private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
13
+
14
+    public function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1')
15
+    {
16
+        $this->issuer = $issuer;
17
+        $this->digits = $digits;
18
+        $this->period = $period;
19
+
20
+        $algorithm = strtolower(trim($algorithm));
21
+        if (!in_array($algorithm, self::$_supportedalgos)) {
22
+            $algorithm = 'sha1';
23
+        }
24
+        $this->algorithm = $algorithm;
25
+
26
+        self::$_base32 = str_split(self::$_base32dict);
27
+        self::$_base32lookup = array_flip(self::$_base32);
21
     }
28
     }
22
-    $this->algorithm = $algorithm;
23
-
24
-    self::$_base32 = str_split(self::$_base32dict);
25
-    self::$_base32lookup = array_flip(self::$_base32);
26
-  }
27
-
28
-  public function createSecret($bits = 210) {
29
-    $secret = '';
30
-    $bytes = ceil($bits / 5); //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
31
-    $rnd = random_bytes($bytes);
32
-    for ($i = 0; $i < $bytes; $i++) {
33
-      $secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
29
+
30
+    public function createSecret($bits = 210)
31
+    {
32
+        $secret = '';
33
+        $bytes = ceil($bits / 5); //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
34
+        $rnd = random_bytes($bytes);
35
+        for ($i = 0; $i < $bytes; $i++) {
36
+            $secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
37
+        }
38
+        return $secret;
34
     }
39
     }
35
-    return $secret;
36
-  }
37
-
38
-  // Calculate the code with given secret and point in time
39
-  public function getCode($secret, $time = null) {
40
-    $secretkey = $this->base32Decode($secret);
41
-
42
-    $timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time)));  // Pack time into binary string
43
-    $hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true);             // Hash it with users secret key
44
-    $hashpart = substr($hashhmac, ord(substr($hashhmac, -1)) & 0x0F, 4);               // Use last nibble of result as index/offset and grab 4 bytes of the result
45
-    $value = unpack('N', $hashpart);                                                   // Unpack binary value
46
-    $value = $value[1] & 0x7FFFFFFF;                                                   // Drop MSB, keep only 31 bits
47
-
48
-    return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
49
-  }
50
-
51
-  // Check if the code is correct. This will accept codes starting from now +/- ($discrepancy * period) seconds
52
-  public function verifyCode($secret, $code, $discrepancy = 1, $time = null) {
53
-    $result = false;
54
-    $timetamp = $this->getTime($time);
55
-
56
-    // Always iterate all possible codes to prevent timing-attacks
57
-    for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
58
-      $result |= hash_equals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
40
+
41
+    // Calculate the code with given secret and point in time
42
+    public function getCode($secret, $time = null)
43
+    {
44
+        $secretkey = $this->base32Decode($secret);
45
+
46
+        $timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time)));  // Pack time into binary string
47
+        $hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true);             // Hash it with users secret key
48
+        $hashpart = substr($hashhmac, ord(substr($hashhmac, -1)) & 0x0F, 4);               // Use last nibble of result as index/offset and grab 4 bytes of the result
49
+        $value = unpack('N', $hashpart);                                                   // Unpack binary value
50
+        $value = $value[1] & 0x7FFFFFFF;                                                   // Drop MSB, keep only 31 bits
51
+
52
+        return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
59
     }
53
     }
60
 
54
 
61
-    return (bool)$result;
62
-  }
63
-
64
-  // Get data-uri of QRCode
65
-  public function getQRCodeImageAsDataUri($label, $secret, $size = 300) {
66
-
67
-    if (exec('which qrencode')) {
68
-      $QRCodeImage = shell_exec("qrencode -s ".(int)($size/40)." -m 3 -o - '".$this->getQRText($label, $secret)."'");
69
-    } else {
70
-      $curlhandle = curl_init();
71
-
72
-      curl_setopt_array($curlhandle, array(
73
-        CURLOPT_URL => 'https://chart.googleapis.com/chart?cht=qr&chs='.$size.'x'.$size.'&chld=L|1&chl='.rawurlencode($this->getQRText($label, $secret)),
74
-        CURLOPT_RETURNTRANSFER => true,
75
-        CURLOPT_CONNECTTIMEOUT => 10,
76
-        CURLOPT_DNS_CACHE_TIMEOUT => 10,
77
-        CURLOPT_TIMEOUT => 10,
78
-        CURLOPT_SSL_VERIFYPEER => false,
79
-        CURLOPT_USERAGENT => 'TwoFactorAuth'
80
-      ));
81
-
82
-      $QRCodeImage = curl_exec($curlhandle);
83
-      curl_close($curlhandle);
55
+    // Check if the code is correct. This will accept codes starting from now +/- ($discrepancy * period) seconds
56
+    public function verifyCode($secret, $code, $discrepancy = 1, $time = null)
57
+    {
58
+        $result = false;
59
+        $timetamp = $this->getTime($time);
60
+
61
+        // Always iterate all possible codes to prevent timing-attacks
62
+        for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
63
+            $result |= hash_equals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
64
+        }
65
+
66
+        return (bool)$result;
84
     }
67
     }
85
 
68
 
86
-    return 'data:image/png;base64,'.base64_encode($QRCodeImage);
87
-  }
88
-
89
-  private function getTime($time) {
90
-    return ($time === null) ? time() : $time;
91
-  }
92
-
93
-  private function getTimeSlice($time = null, $offset = 0) {
94
-    return (int)floor($time / $this->period) + ($offset * $this->period);
95
-  }
96
-
97
-  // Builds a string to be encoded in a QR code
98
-  public function getQRText($label, $secret) {
99
-    $QRText = 'otpauth://totp/'.rawurlencode($label).'?secret='.rawurlencode($secret);
100
-    $QRText .= ($this->issuer) ? '&issuer='.rawurlencode($this->issuer) : '';
101
-    $QRText .= ($this->period != 30) ? '&period='.intval($this->period) : '';
102
-    $QRText .= ($this->algorithm != 'sha1') ? '&algorithm='.rawurlencode(strtoupper($this->algorithm)) : '';
103
-    $QRText .= ($this->digits != 6) ? '&digits='.intval($this->digits) : '';
104
-    return $QRText;
105
-  }
106
-
107
-  private function base32Decode($value) {
108
-    if (strlen($value)==0) { return ''; }
109
-
110
-    $buffer = '';
111
-    foreach (str_split($value) as $char) {
112
-      if ($char !== '=') {
113
-        $buffer .= str_pad(decbin(self::$_base32lookup[$char]), 5, 0, STR_PAD_LEFT);
114
-      }
69
+    // Get data-uri of QRCode
70
+    public function getQRCodeImageAsDataUri($label, $secret, $size = 300)
71
+    {
72
+        if (exec('which qrencode')) {
73
+            $QRCodeImage = shell_exec("qrencode -s ".(int)($size/40)." -m 3 -o - '".$this->getQRText($label, $secret)."'");
74
+        } else {
75
+            $curlhandle = curl_init();
76
+
77
+            curl_setopt_array($curlhandle, array(
78
+            CURLOPT_URL => 'https://chart.googleapis.com/chart?cht=qr&chs='.$size.'x'.$size.'&chld=L|1&chl='.rawurlencode($this->getQRText($label, $secret)),
79
+            CURLOPT_RETURNTRANSFER => true,
80
+            CURLOPT_CONNECTTIMEOUT => 10,
81
+            CURLOPT_DNS_CACHE_TIMEOUT => 10,
82
+            CURLOPT_TIMEOUT => 10,
83
+            CURLOPT_SSL_VERIFYPEER => false,
84
+            CURLOPT_USERAGENT => 'TwoFactorAuth'
85
+            ));
86
+
87
+            $QRCodeImage = curl_exec($curlhandle);
88
+            curl_close($curlhandle);
89
+        }
90
+
91
+        return 'data:image/png;base64,'.base64_encode($QRCodeImage);
115
     }
92
     }
116
-    $length = strlen($buffer);
117
-    $blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
118
 
93
 
119
-    $output = '';
120
-    foreach (explode(' ', $blocks) as $block) {
121
-      $output .= chr(bindec(str_pad($block, 8, 0, STR_PAD_RIGHT)));
94
+    private function getTime($time)
95
+    {
96
+        return ($time === null) ? time() : $time;
122
     }
97
     }
123
 
98
 
124
-    return $output;
125
-  }
99
+    private function getTimeSlice($time = null, $offset = 0)
100
+    {
101
+        return (int)floor($time / $this->period) + ($offset * $this->period);
102
+    }
103
+
104
+    // Builds a string to be encoded in a QR code
105
+    public function getQRText($label, $secret)
106
+    {
107
+        $QRText = 'otpauth://totp/'.rawurlencode($label).'?secret='.rawurlencode($secret);
108
+        $QRText .= ($this->issuer) ? '&issuer='.rawurlencode($this->issuer) : '';
109
+        $QRText .= ($this->period != 30) ? '&period='.intval($this->period) : '';
110
+        $QRText .= ($this->algorithm != 'sha1') ? '&algorithm='.rawurlencode(strtoupper($this->algorithm)) : '';
111
+        $QRText .= ($this->digits != 6) ? '&digits='.intval($this->digits) : '';
112
+        return $QRText;
113
+    }
114
+
115
+    private function base32Decode($value)
116
+    {
117
+        if (strlen($value)==0) {
118
+            return '';
119
+        }
120
+
121
+        $buffer = '';
122
+        foreach (str_split($value) as $char) {
123
+            if ($char !== '=') {
124
+                $buffer .= str_pad(decbin(self::$_base32lookup[$char]), 5, 0, STR_PAD_LEFT);
125
+            }
126
+        }
127
+        $length = strlen($buffer);
128
+        $blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
129
+
130
+        $output = '';
131
+        foreach (explode(' ', $blocks) as $block) {
132
+            $output .= chr(bindec(str_pad($block, 8, 0, STR_PAD_RIGHT)));
133
+        }
134
+
135
+        return $output;
136
+    }
126
 }
137
 }

+ 345
- 327
classes/u2f.class.php View File

73
 
73
 
74
 const PUBKEY_LEN = 65;
74
 const PUBKEY_LEN = 65;
75
 
75
 
76
-class U2F {
77
-  private $appId;
76
+class U2F
77
+{
78
+    private $appId;
78
 
79
 
79
-  private $attestDir;
80
+    private $attestDir;
80
 
81
 
81
-  private $FIXCERTS = array(
82
+    private $FIXCERTS = array(
82
     '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
83
     '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
83
     'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
84
     'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
84
     '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
85
     '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
85
     'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
86
     'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
86
     '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
87
     '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
87
     'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
88
     'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
88
-  );
89
-
90
-  /**
91
-   * @param string $appId Application id for the running application
92
-   * @param string|null $attestDir Directory where trusted attestation roots may be found
93
-   * @throws Error If OpenSSL older than 1.0.0 is used
94
-   */
95
-  public function __construct($appId, $attestDir = null) {
96
-    if (OPENSSL_VERSION_NUMBER < 0x10000000) {
97
-      throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
98
-    }
99
-    $this->appId = $appId;
100
-    $this->attestDir = $attestDir;
101
-  }
102
-
103
-  /**
104
-   * Called to get a registration request to send to a user.
105
-   * Returns an array of one registration request and a array of sign requests.
106
-   *
107
-   * @param array $registrations List of current registrations for this
108
-   * user, to prevent the user from registering the same authenticator several
109
-   * times.
110
-   * @return array An array of two elements, the first containing a
111
-   * RegisterRequest the second being an array of SignRequest
112
-   * @throws Error
113
-   */
114
-  public function getRegisterData(array $registrations = []) {
115
-    $challenge = $this->createChallenge();
116
-    $request = new RegisterRequest($challenge, $this->appId);
117
-    $signs = $this->getAuthenticateData($registrations);
118
-    return [$request, $signs];
119
-  }
120
-
121
-  /**
122
-   * Called to verify and unpack a registration message.
123
-   *
124
-   * @param RegisterRequest $request this is a reply to
125
-   * @param object $response response from a user
126
-   * @param bool $includeCert set to true if the attestation certificate should be
127
-   * included in the returned Registration object
128
-   * @return Registration
129
-   * @throws Error
130
-   */
131
-  public function doRegister($request, $response, $includeCert = true) {
132
-    if (!is_object($request)) {
133
-      throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
134
-    }
89
+    );
135
 
90
 
136
-    if (!is_object($response)) {
137
-      throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
91
+    /**
92
+     * @param string $appId Application id for the running application
93
+     * @param string|null $attestDir Directory where trusted attestation roots may be found
94
+     * @throws Error If OpenSSL older than 1.0.0 is used
95
+     */
96
+    public function __construct($appId, $attestDir = null)
97
+    {
98
+        if (OPENSSL_VERSION_NUMBER < 0x10000000) {
99
+            throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
100
+        }
101
+        $this->appId = $appId;
102
+        $this->attestDir = $attestDir;
138
     }
103
     }
139
 
104
 
140
-    if (property_exists($response, 'errorCode') && $response->errorCode !== 0) {
141
-      throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
105
+    /**
106
+     * Called to get a registration request to send to a user.
107
+     * Returns an array of one registration request and a array of sign requests.
108
+     *
109
+     * @param array $registrations List of current registrations for this
110
+     * user, to prevent the user from registering the same authenticator several
111
+     * times.
112
+     * @return array An array of two elements, the first containing a
113
+     * RegisterRequest the second being an array of SignRequest
114
+     * @throws Error
115
+     */
116
+    public function getRegisterData(array $registrations = [])
117
+    {
118
+        $challenge = $this->createChallenge();
119
+        $request = new RegisterRequest($challenge, $this->appId);
120
+        $signs = $this->getAuthenticateData($registrations);
121
+        return [$request, $signs];
142
     }
122
     }
143
 
123
 
144
-    if (!is_bool($includeCert)) {
145
-      throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
146
-    }
124
+    /**
125
+     * Called to verify and unpack a registration message.
126
+     *
127
+     * @param RegisterRequest $request this is a reply to
128
+     * @param object $response response from a user
129
+     * @param bool $includeCert set to true if the attestation certificate should be
130
+     * included in the returned Registration object
131
+     * @return Registration
132
+     * @throws Error
133
+     */
134
+    public function doRegister($request, $response, $includeCert = true)
135
+    {
136
+        if (!is_object($request)) {
137
+            throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
138
+        }
147
 
139
 
148
-    $rawReg = $this->base64u_decode($response->registrationData);
149
-    $regData = array_values(unpack('C*', $rawReg));
150
-    $clientData = $this->base64u_decode($response->clientData);
151
-    $cli = json_decode($clientData);
140
+        if (!is_object($response)) {
141
+            throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
142
+        }
152
 
143
 
153
-    if ($cli->challenge !== $request->challenge) {
154
-      throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
155
-    }
144
+        if (property_exists($response, 'errorCode') && $response->errorCode !== 0) {
145
+            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING);
146
+        }
156
 
147
 
157
-    $registration = new Registration();
158
-    $offs = 1;
159
-    $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
160
-    $offs += PUBKEY_LEN;
161
-    // decode the pubKey to make sure it's good
162
-    $tmpKey = $this->pubkey_to_pem($pubKey);
163
-    if ($tmpKey === null) {
164
-      throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
165
-    }
166
-    $registration->publicKey = base64_encode($pubKey);
167
-    $khLen = $regData[$offs++];
168
-    $kh = substr($rawReg, $offs, $khLen);
169
-    $offs += $khLen;
170
-    $registration->keyHandle = $this->base64u_encode($kh);
171
-
172
-    // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
173
-    $certLen = 4;
174
-    $certLen += ($regData[$offs + 2] << 8);
175
-    $certLen += $regData[$offs + 3];
176
-
177
-    $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
178
-    $offs += $certLen;
179
-    $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
180
-    $pemCert .= chunk_split(base64_encode($rawCert), 64);
181
-    $pemCert .= "-----END CERTIFICATE-----";
182
-    if ($includeCert) {
183
-      $registration->certificate = base64_encode($rawCert);
184
-    }
185
-    if ($this->attestDir) {
186
-      if (openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
187
-        throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
188
-      }
189
-    }
148
+        if (!is_bool($includeCert)) {
149
+            throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
150
+        }
190
 
151
 
191
-    if (!openssl_pkey_get_public($pemCert)) {
192
-      throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
193
-    }
194
-    $signature = substr($rawReg, $offs);
195
-
196
-    $dataToVerify  = chr(0);
197
-    $dataToVerify .= hash('sha256', $request->appId, true);
198
-    $dataToVerify .= hash('sha256', $clientData, true);
199
-    $dataToVerify .= $kh;
200
-    $dataToVerify .= $pubKey;
201
-
202
-    if (openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
203
-      return $registration;
204
-    } else {
205
-      throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
206
-    }
207
-  }
208
-
209
-  /**
210
-   * Called to get an authentication request.
211
-   *
212
-   * @param array $registrations An array of the registrations to create authentication requests for.
213
-   * @return array An array of SignRequest
214
-   * @throws Error
215
-   */
216
-  public function getAuthenticateData(array $registrations) {
217
-    $sigs = array();
218
-    $challenge = $this->createChallenge();
219
-    foreach ($registrations as $reg) {
220
-      if (!is_object($reg)) {
221
-        throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
222
-      }
223
-
224
-      $sig = new SignRequest();
225
-      $sig->appId = $this->appId;
226
-      $sig->keyHandle = $reg->keyHandle;
227
-      $sig->challenge = $challenge;
228
-      $sigs[] = $sig;
229
-    }
230
-    return $sigs;
231
-  }
232
-
233
-  /**
234
-   * Called to verify an authentication response
235
-   *
236
-   * @param array $requests An array of outstanding authentication requests
237
-   * @param array $registrations An array of current registrations
238
-   * @param object $response A response from the authenticator
239
-   * @return Registration
240
-   * @throws Error
241
-   *
242
-   * The Registration object returned on success contains an updated counter
243
-   * that should be saved for future authentications.
244
-   * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
245
-   * token cloning or similar and appropriate action should be taken.
246
-   */
247
-  public function doAuthenticate(array $requests, array $registrations, $response) {
248
-    if (!is_object($response)) {
249
-      throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
152
+        $rawReg = $this->base64u_decode($response->registrationData);
153
+        $regData = array_values(unpack('C*', $rawReg));
154
+        $clientData = $this->base64u_decode($response->clientData);
155
+        $cli = json_decode($clientData);
156
+
157
+        if ($cli->challenge !== $request->challenge) {
158
+            throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE);
159
+        }
160
+
161
+        $registration = new Registration();
162
+        $offs = 1;
163
+        $pubKey = substr($rawReg, $offs, PUBKEY_LEN);
164
+        $offs += PUBKEY_LEN;
165
+        // decode the pubKey to make sure it's good
166
+        $tmpKey = $this->pubkey_to_pem($pubKey);
167
+        if ($tmpKey === null) {
168
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE);
169
+        }
170
+        $registration->publicKey = base64_encode($pubKey);
171
+        $khLen = $regData[$offs++];
172
+        $kh = substr($rawReg, $offs, $khLen);
173
+        $offs += $khLen;
174
+        $registration->keyHandle = $this->base64u_encode($kh);
175
+
176
+        // length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
177
+        $certLen = 4;
178
+        $certLen += ($regData[$offs + 2] << 8);
179
+        $certLen += $regData[$offs + 3];
180
+
181
+        $rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
182
+        $offs += $certLen;
183
+        $pemCert  = "-----BEGIN CERTIFICATE-----\r\n";
184
+        $pemCert .= chunk_split(base64_encode($rawCert), 64);
185
+        $pemCert .= "-----END CERTIFICATE-----";
186
+        if ($includeCert) {
187
+            $registration->certificate = base64_encode($rawCert);
188
+        }
189
+        if ($this->attestDir) {
190
+            if (openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
191
+                throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION);
192
+            }
193
+        }
194
+
195
+        if (!openssl_pkey_get_public($pemCert)) {
196
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE);
197
+        }
198
+        $signature = substr($rawReg, $offs);
199
+
200
+        $dataToVerify  = chr(0);
201
+        $dataToVerify .= hash('sha256', $request->appId, true);
202
+        $dataToVerify .= hash('sha256', $clientData, true);
203
+        $dataToVerify .= $kh;
204
+        $dataToVerify .= $pubKey;
205
+
206
+        if (openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
207
+            return $registration;
208
+        } else {
209
+            throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE);
210
+        }
250
     }
211
     }
251
 
212
 
252
-    if (property_exists($response, 'errorCode') && $response->errorCode !== 0) {
253
-      throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
213
+    /**
214
+     * Called to get an authentication request.
215
+     *
216
+     * @param array $registrations An array of the registrations to create authentication requests for.
217
+     * @return array An array of SignRequest
218
+     * @throws Error
219
+     */
220
+    public function getAuthenticateData(array $registrations)
221
+    {
222
+        $sigs = array();
223
+        $challenge = $this->createChallenge();
224
+        foreach ($registrations as $reg) {
225
+            if (!is_object($reg)) {
226
+                throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
227
+            }
228
+
229
+            $sig = new SignRequest();
230
+            $sig->appId = $this->appId;
231
+            $sig->keyHandle = $reg->keyHandle;
232
+            $sig->challenge = $challenge;
233
+            $sigs[] = $sig;
234
+        }
235
+        return $sigs;
254
     }
236
     }
255
 
237
 
256
-    $req = null;
257
-    $reg = null;
238
+    /**
239
+     * Called to verify an authentication response
240
+     *
241
+     * @param array $requests An array of outstanding authentication requests
242
+     * @param array $registrations An array of current registrations
243
+     * @param object $response A response from the authenticator
244
+     * @return Registration
245
+     * @throws Error
246
+     *
247
+     * The Registration object returned on success contains an updated counter
248
+     * that should be saved for future authentications.
249
+     * If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
250
+     * token cloning or similar and appropriate action should be taken.
251
+     */
252
+    public function doAuthenticate(array $requests, array $registrations, $response)
253
+    {
254
+        if (!is_object($response)) {
255
+            throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
256
+        }
257
+
258
+        if (property_exists($response, 'errorCode') && $response->errorCode !== 0) {
259
+            throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING);
260
+        }
258
 
261
 
259
-    $clientData = $this->base64u_decode($response->clientData);
260
-    $decodedClient = json_decode($clientData);
261
-    foreach ($requests as $req) {
262
-      if (!is_object($req)) {
263
-        throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
264
-      }
262
+        $req = null;
263
+        $reg = null;
265
 
264
 
266
-      if ($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
267
-        break;
268
-      }
265
+        $clientData = $this->base64u_decode($response->clientData);
266
+        $decodedClient = json_decode($clientData);
267
+        foreach ($requests as $req) {
268
+            if (!is_object($req)) {
269
+                throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
270
+            }
269
 
271
 
270
-      $req = null;
271
-    }
272
-    if ($req === null) {
273
-      throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
274
-    }
275
-    foreach ($registrations as $reg) {
276
-      if (!is_object($reg)) {
277
-        throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
278
-      }
279
-
280
-      if ($reg->keyHandle === $response->keyHandle) {
281
-        break;
282
-      }
283
-      $reg = null;
272
+            if ($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
273
+                break;
274
+            }
275
+
276
+            $req = null;
277
+        }
278
+        if ($req === null) {
279
+            throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST);
280
+        }
281
+        foreach ($registrations as $reg) {
282
+            if (!is_object($reg)) {
283
+                throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
284
+            }
285
+
286
+            if ($reg->keyHandle === $response->keyHandle) {
287
+                break;
288
+            }
289
+            $reg = null;
290
+        }
291
+        if ($reg === null) {
292
+            throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION);
293
+        }
294
+        $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
295
+        if ($pemKey === null) {
296
+            throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE);
297
+        }
298
+
299
+        $signData = $this->base64u_decode($response->signatureData);
300
+        $dataToVerify  = hash('sha256', $req->appId, true);
301
+        $dataToVerify .= substr($signData, 0, 5);
302
+        $dataToVerify .= hash('sha256', $clientData, true);
303
+        $signature = substr($signData, 5);
304
+
305
+        if (openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
306
+            $ctr = unpack("Nctr", substr($signData, 1, 4));
307
+            $counter = $ctr['ctr'];
308
+            /* TODO: wrap-around should be handled somehow.. */
309
+            if ($counter > $reg->counter) {
310
+                $reg->counter = $counter;
311
+                return $reg;
312
+            } else {
313
+                throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW);
314
+            }
315
+        } else {
316
+            throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE);
317
+        }
284
     }
318
     }
285
-    if ($reg === null) {
286
-      throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
319
+
320
+    /**
321
+     * @return array
322
+     */
323
+    private function get_certs()
324
+    {
325
+        $files = array();
326
+        $dir = $this->attestDir;
327
+        if ($dir && $handle = opendir($dir)) {
328
+            while (false !== ($entry = readdir($handle))) {
329
+                if (is_file("$dir/$entry")) {
330
+                    $files[] = "$dir/$entry";
331
+                }
332
+            }
333
+            closedir($handle);
334
+        }
335
+        return $files;
287
     }
336
     }
288
-    $pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
289
-    if ($pemKey === null) {
290
-      throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
337
+
338
+    /**
339
+     * @param string $data
340
+     * @return string
341
+     */
342
+    private function base64u_encode($data)
343
+    {
344
+        return trim(strtr(base64_encode($data), '+/', '-_'), '=');
291
     }
345
     }
292
 
346
 
293
-    $signData = $this->base64u_decode($response->signatureData);
294
-    $dataToVerify  = hash('sha256', $req->appId, true);
295
-    $dataToVerify .= substr($signData, 0, 5);
296
-    $dataToVerify .= hash('sha256', $clientData, true);
297
-    $signature = substr($signData, 5);
298
-
299
-    if (openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
300
-      $ctr = unpack("Nctr", substr($signData, 1, 4));
301
-      $counter = $ctr['ctr'];
302
-      /* TODO: wrap-around should be handled somehow.. */
303
-      if ($counter > $reg->counter) {
304
-        $reg->counter = $counter;
305
-        return $reg;
306
-      } else {
307
-        throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
308
-      }
309
-    } else {
310
-      throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
347
+    /**
348
+     * @param string $data
349
+     * @return string
350
+     */
351
+    private function base64u_decode($data)
352
+    {
353
+        return base64_decode(strtr($data, '-_', '+/'));
311
     }
354
     }
312
-  }
313
-
314
-  /**
315
-   * @return array
316
-   */
317
-  private function get_certs() {
318
-    $files = array();
319
-    $dir = $this->attestDir;
320
-    if ($dir && $handle = opendir($dir)) {
321
-      while(false !== ($entry = readdir($handle))) {
322
-        if (is_file("$dir/$entry")) {
323
-          $files[] = "$dir/$entry";
355
+
356
+    /**
357
+     * @param string $key
358
+     * @return null|string
359
+     */
360
+    private function pubkey_to_pem($key)
361
+    {
362
+        if (strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
363
+            return null;
324
         }
364
         }
325
-      }
326
-      closedir($handle);
327
-    }
328
-    return $files;
329
-  }
330
-
331
-  /**
332
-   * @param string $data
333
-   * @return string
334
-   */
335
-  private function base64u_encode($data) {
336
-    return trim(strtr(base64_encode($data), '+/', '-_'), '=');
337
-  }
338
-
339
-  /**
340
-   * @param string $data
341
-   * @return string
342
-   */
343
-  private function base64u_decode($data) {
344
-    return base64_decode(strtr($data, '-_', '+/'));
345
-  }
346
-
347
-  /**
348
-   * @param string $key
349
-   * @return null|string
350
-   */
351
-  private function pubkey_to_pem($key) {
352
-    if (strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
353
-      return null;
365
+
366
+        /*
367
+         * Convert the public key to binary DER format first
368
+         * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
369
+         *
370
+         *  SEQUENCE(2 elem)                        30 59
371
+         *   SEQUENCE(2 elem)                       30 13
372
+         *    OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
373
+         *    OID1.2.840.10045.3.1.7 (secp256r1)    06 08 2a 86 48 ce 3d 03 01 07
374
+         *   BIT STRING(520 bit)                    03 42 ..key..
375
+         */
376
+        $der  = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
377
+        $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
378
+        $der .= "\0".$key;
379
+
380
+        $pem  = "-----BEGIN PUBLIC KEY-----\r\n";
381
+        $pem .= chunk_split(base64_encode($der), 64);
382
+        $pem .= "-----END PUBLIC KEY-----";
383
+
384
+        return $pem;
354
     }
385
     }
355
 
386
 
356
-    /*
357
-     * Convert the public key to binary DER format first
358
-     * Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
359
-     *
360
-     *  SEQUENCE(2 elem)                        30 59
361
-     *   SEQUENCE(2 elem)                       30 13
362
-     *    OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
363
-     *    OID1.2.840.10045.3.1.7 (secp256r1)    06 08 2a 86 48 ce 3d 03 01 07
364
-     *   BIT STRING(520 bit)                    03 42 ..key..
387
+    /**
388
+     * @return string
389
+     * @throws Error
365
      */
390
      */
366
-    $der  = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
367
-    $der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
368
-    $der .= "\0".$key;
369
-
370
-    $pem  = "-----BEGIN PUBLIC KEY-----\r\n";
371
-    $pem .= chunk_split(base64_encode($der), 64);
372
-    $pem .= "-----END PUBLIC KEY-----";
373
-
374
-    return $pem;
375
-  }
376
-
377
-  /**
378
-   * @return string
379
-   * @throws Error
380
-   */
381
-  private function createChallenge() {
382
-    $challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
383
-    if ($crypto_strong !== true) {
384
-      throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
385
-    }
391
+    private function createChallenge()
392
+    {
393
+        $challenge = openssl_random_pseudo_bytes(32, $crypto_strong);
394
+        if ($crypto_strong !== true) {
395
+            throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
396
+        }
386
 
397
 
387
-    $challenge = $this->base64u_encode( $challenge );
398
+        $challenge = $this->base64u_encode($challenge);
388
 
399
 
389
-    return $challenge;
390
-  }
400
+        return $challenge;
401
+    }
391
 
402
 
392
-  /**
393
-   * Fixes a certificate where the signature contains unused bits.
394
-   *
395
-   * @param string $cert
396
-   * @return mixed
397
-   */
398
-  private function fixSignatureUnusedBits($cert) {
399
-    if (in_array(hash('sha256', $cert), $this->FIXCERTS)) {
400
-      $cert[strlen($cert) - 257] = "\0";
403
+    /**
404
+     * Fixes a certificate where the signature contains unused bits.
405
+     *
406
+     * @param string $cert
407
+     * @return mixed
408
+     */
409
+    private function fixSignatureUnusedBits($cert)
410
+    {
411
+        if (in_array(hash('sha256', $cert), $this->FIXCERTS)) {
412
+            $cert[strlen($cert) - 257] = "\0";
413
+        }
414
+        return $cert;
401
     }
415
     }
402
-    return $cert;
403
-  }
404
 }
416
 }
405
 
417
 
406
 /**
418
 /**
408
  *
420
  *
409
  * @package u2flib_server
421
  * @package u2flib_server
410
  */
422
  */
411
-class RegisterRequest {
412
-  public $version = U2F_VERSION;
413
-
414
-  public $challenge;
415
-
416
-  public $appId;
417
-
418
-  /**
419
-   * @param string $challenge
420
-   * @param string $appId
421
-   * @internal
422
-   */
423
-  public function __construct($challenge, $appId) {
424
-    $this->challenge = $challenge;
425
-    $this->appId = $appId;
426
-  }
423
+class RegisterRequest
424
+{
425
+    public $version = U2F_VERSION;
426
+
427
+    public $challenge;
428
+
429
+    public $appId;
430
+
431
+    /**
432
+     * @param string $challenge
433
+     * @param string $appId
434
+     * @internal
435
+     */
436
+    public function __construct($challenge, $appId)
437
+    {
438
+        $this->challenge = $challenge;
439
+        $this->appId = $appId;
440
+    }
427
 }
441
 }
428
 
442
 
429
 /**
443
 /**
431
  *
445
  *
432
  * @package u2flib_server
446
  * @package u2flib_server
433
  */
447
  */
434
-class SignRequest {
435
-  public $version = U2F_VERSION;
448
+class SignRequest
449
+{
450
+    public $version = U2F_VERSION;
436
 
451
 
437
-  public $challenge;
452
+    public $challenge;
438
 
453
 
439
-  public $keyHandle;
454
+    public $keyHandle;
440
 
455
 
441
-  public $appId;
456
+    public $appId;
442
 }
457
 }
443
 
458
 
444
 /**
459
 /**
446
  *
461
  *
447
  * @package u2flib_server
462
  * @package u2flib_server
448
  */
463
  */
449
-class Registration {
450
-  public $keyHandle;
464
+class Registration
465
+{
466
+    public $keyHandle;
451
 
467
 
452
-  public $publicKey;
468
+    public $publicKey;
453
 
469
 
454
-  public $certificate;
470
+    public $certificate;
455
 
471
 
456
-  public $counter = -1;
472
+    public $counter = -1;
457
 }
473
 }
458
 
474
 
459
 /**
475
 /**
461
  *
477
  *
462
  * @package u2flib_server
478
  * @package u2flib_server
463
  */
479
  */
464
-class Error extends \Exception {
465
-  /**
466
-   * Override constructor and make message and code mandatory
467
-   * @param string $message
468
-   * @param int $code
469
-   * @param \Exception|null $previous
470
-   */
471
-  public function __construct($message, $code, \Exception $previous = null) {
472
-    parent::__construct($message, $code, $previous);
473
-  }
480
+class Error extends \Exception
481
+{
482
+    /**
483
+     * Override constructor and make message and code mandatory
484
+     * @param string $message
485
+     * @param int $code
486
+     * @param \Exception|null $previous
487
+     */
488
+    public function __construct($message, $code, \Exception $previous = null)
489
+    {
490
+        parent::__construct($message, $code, $previous);
491
+    }
474
 }
492
 }

+ 44
- 40
classes/useragent.class.php View File

1
-<?
2
-class UserAgent {
3
-  private static $Browsers = array(
1
+<?php
2
+class UserAgent
3
+{
4
+    private static $Browsers = array(
4
     //Less popular
5
     //Less popular
5
     'Shiira'     => 'Shiira',
6
     'Shiira'     => 'Shiira',
6
     'Songbird'   => 'Songbird',
7
     'Songbird'   => 'Songbird',
43
     'Java'          => 'Java',
44
     'Java'          => 'Java',
44
     'RSS'           => 'RSS Downloader'
45
     'RSS'           => 'RSS Downloader'
45
     */
46
     */
46
-  );
47
+    );
47
 
48
 
48
-  private static $OperatingSystems = array(
49
+    private static $OperatingSystems = array(
49
     //Mobile
50
     //Mobile
50
     'SymbianOS'    => 'Symbian',
51
     'SymbianOS'    => 'Symbian',
51
     'blackberry'   => 'BlackBerry',
52
     'blackberry'   => 'BlackBerry',
115
     //Catch-all
116
     //Catch-all
116
     'win' => 'Windows',
117
     'win' => 'Windows',
117
     'mac' => 'Mac OS X'
118
     'mac' => 'Mac OS X'
118
-  );
119
+    );
119
 
120
 
120
-  public static function operating_system(&$UserAgentString) {
121
-    if (empty($UserAgentString)) {
122
-      return 'Hidden';
121
+    public static function operating_system(&$UserAgentString)
122
+    {
123
+        if (empty($UserAgentString)) {
124
+            return 'Hidden';
125
+        }
126
+        foreach (self::$OperatingSystems as $String => $OperatingSystem) {
127
+            if (stripos($UserAgentString, $String) !== false) {
128
+                return $OperatingSystem;
129
+            }
130
+        }
131
+        return 'Unknown';
123
     }
132
     }
124
-    foreach (self::$OperatingSystems as $String => $OperatingSystem) {
125
-      if (stripos($UserAgentString, $String) !== false) {
126
-        return $OperatingSystem;
127
-      }
128
-    }
129
-    return 'Unknown';
130
-  }
131
 
133
 
132
-  public static function mobile(&$UserAgentString) {
133
-    if (strpos($UserAgentString, 'iPad') !== false) {
134
-      return false;
135
-    }
134
+    public static function mobile(&$UserAgentString)
135
+    {
136
+        if (strpos($UserAgentString, 'iPad') !== false) {
137
+            return false;
138
+        }
136
 
139
 
137
-    // "Mobi" catches "Mobile" too
138
-    if (strpos($UserAgentString, 'Device') || strpos($UserAgentString, 'Mobi') || strpos($UserAgentString, 'Mini') || strpos($UserAgentString, 'webOS')) {
139
-      return true;
140
+        // "Mobi" catches "Mobile" too
141
+        if (strpos($UserAgentString, 'Device') || strpos($UserAgentString, 'Mobi') || strpos($UserAgentString, 'Mini') || strpos($UserAgentString, 'webOS')) {
142
+            return true;
143
+        }
144
+        return false;
140
     }
145
     }
141
-    return false;
142
-  }
143
 
146
 
144
-  public static function browser(&$UserAgentString) {
145
-    if (empty($UserAgentString)) {
146
-      return 'Hidden';
147
-    }
148
-    $Return = 'Unknown';
149
-    foreach (self::$Browsers as $String => $Browser) {
150
-      if (strpos($UserAgentString, $String) !== false) {
151
-        $Return = $Browser;
152
-        break;
153
-      }
154
-    }
155
-    if (self::mobile($UserAgentString)) {
156
-      $Return .= ' Mobile';
147
+    public static function browser(&$UserAgentString)
148
+    {
149
+        if (empty($UserAgentString)) {
150
+            return 'Hidden';
151
+        }
152
+        $Return = 'Unknown';
153
+        foreach (self::$Browsers as $String => $Browser) {
154
+            if (strpos($UserAgentString, $String) !== false) {
155
+                $Return = $Browser;
156
+                break;
157
+            }
158
+        }
159
+        if (self::mobile($UserAgentString)) {
160
+            $Return .= ' Mobile';
161
+        }
162
+        return $Return;
157
     }
163
     }
158
-    return $Return;
159
-  }
160
 }
164
 }

Loading…
Cancel
Save