summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobThree <rob@devcorner.nl>2014-09-24 23:33:15 +0200
committerRobThree <rob@devcorner.nl>2014-09-24 23:33:15 +0200
commit004739be326c49a3a92f32109e0578d60acd63e7 (patch)
tree36317c63b30e6b65762f630fc8877f7577fd2395
parentb50661c60acb9a48fa0c8fad688decb91b527f8e (diff)
downloadTwoFactorAuth-004739be326c49a3a92f32109e0578d60acd63e7.zip
TwoFactorAuth-004739be326c49a3a92f32109e0578d60acd63e7.tar.gz
TwoFactorAuth-004739be326c49a3a92f32109e0578d60acd63e7.tar.bz2
* Using namespace(s): https://www.reddit.com/r/programming/comments/2gwqcr/multifactor_authentication_or_twofactor_auth_for/ckr9e1g
* Added simple "PSR-4" autoloader * Classes and interfaces to separate files * Updated README for namespaces
-rw-r--r--README.md16
-rw-r--r--RobThree/TwoFactorAuth/Providers/BaseHTTPQRCodeProvider.php29
-rw-r--r--RobThree/TwoFactorAuth/Providers/GoogleQRCodeProvider.php39
-rw-r--r--RobThree/TwoFactorAuth/Providers/IQRCodeProvider.php9
-rw-r--r--RobThree/TwoFactorAuth/Providers/QRServerProvider.php70
-rw-r--r--RobThree/TwoFactorAuth/Providers/QRicketProvider.php53
-rw-r--r--RobThree/TwoFactorAuth/TwoFactorAuth.php147
-rw-r--r--TwoFactorAuth.phpproj12
-rw-r--r--demo.php9
-rw-r--r--loader.php50
-rw-r--r--src/TwoFactorAuth.php329
11 files changed, 420 insertions, 343 deletions
diff --git a/README.md b/README.md
index 334fd20..8dbba30 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,7 @@ Here are some code snippets that should help you get started...
````php
// Start by including the TwoFactorAuth.php file which contains all you need (for now)
-require_once 'src/TwoFactorAuth.php';
-$tfa = new TwoFactorAuth('My Company');
+$tfa = new RobThree\TwoFactorAuth\TwoFactorAuth('My Company');
````
The TwoFactorAuth class constructor accepts 5 parameters (all optional):
@@ -109,11 +108,13 @@ The `getMimeType()` method should return the [MIME type](http://en.wikipedia.org
All you need to do is return the QR-code as binary image data and you're done. All parts of the `$qrtext` have been escaped for you (but note: you *may* need to escape the entire `$qrtext` just once more when passing the data to another server as GET-parameter).
-Let's see if we can use [PHP QR Code](http://phpqrcode.sourceforge.net/) to implement our own, custom, no-3rd-parties-allowed-here, provider. We start with downloading the [required (single) file](https://github.com/t0k4rt/phpqrcode/blob/master/phpqrcode.php) and putting it in our `src/` directory where `TwoFactorAuth.php` is located as well. Now let's implement the provider: create another file named `myprovider.php` in the `src` directory and paste in this content:
+Let's see if we can use [PHP QR Code](http://phpqrcode.sourceforge.net/) to implement our own, custom, no-3rd-parties-allowed-here, provider. We start with downloading the [required (single) file](https://github.com/t0k4rt/phpqrcode/blob/master/phpqrcode.php) and putting it in the directory where `TwoFactorAuth.php` is located as well. Now let's implement the provider: create another file named `myprovider.php` in the `Providers` directory and paste in this content:
````php
<?php
-require_once 'phpqrcode.php'; // Yeah, we're gonna need that
+require_once '../phpqrcode.php'; // Yeah, we're gonna need that
+
+namespace RobThree\TwoFactorAuth\Providers
class MyProvider implements IQRCodeProvider {
public function getMimeType() {
@@ -136,11 +137,8 @@ That's it. We're done! We've implemented our own provider (with help of PHP QR C
````php
<?php
-require_once 'src/TwoFactorAuth.php';
-require_once 'src/myprovider.php';
-
-$mp = new MyProvider();
-$tfa = new TwoFactorAuth('My Company', 6, 30, 'sha1', $mp);
+$mp = new RobThree\TwoFactorAuth\TwoFactorAuth\Providers\MyProvider();
+$tfa = new RobThree\TwoFactorAuth\TwoFactorAuth\TwoFactorAuth('My Company', 6, 30, 'sha1', $mp);
$secret = $tfa->createSecret();
?>
<p><img src="<?php $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret) ?>"></p>
diff --git a/RobThree/TwoFactorAuth/Providers/BaseHTTPQRCodeProvider.php b/RobThree/TwoFactorAuth/Providers/BaseHTTPQRCodeProvider.php
new file mode 100644
index 0000000..f29265d
--- /dev/null
+++ b/RobThree/TwoFactorAuth/Providers/BaseHTTPQRCodeProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace RobThree\TwoFactorAuth\Providers;
+
+abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
+{
+ protected $verifyssl;
+
+ protected function getContent($url)
+ {
+ $ch = curl_init();
+
+ curl_setopt_array($ch, array(
+ CURLOPT_URL => $url,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_MAXREDIRS => 3,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_CONNECTTIMEOUT => 10,
+ CURLOPT_DNS_CACHE_TIMEOUT => 10,
+ CURLOPT_TIMEOUT => 10,
+ CURLOPT_SSL_VERIFYPEER => $this->verifyssl,
+ CURLOPT_USERAGENT => 'TwoFactorAuth'
+ ));
+ $data = curl_exec($ch);
+
+ curl_close($ch);
+ return $data;
+ }
+} \ No newline at end of file
diff --git a/RobThree/TwoFactorAuth/Providers/GoogleQRCodeProvider.php b/RobThree/TwoFactorAuth/Providers/GoogleQRCodeProvider.php
new file mode 100644
index 0000000..5084bac
--- /dev/null
+++ b/RobThree/TwoFactorAuth/Providers/GoogleQRCodeProvider.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace RobThree\TwoFactorAuth\Providers;
+
+// https://developers.google.com/chart/infographics/docs/qr_codes
+class GoogleQRCodeProvider extends BaseHTTPQRCodeProvider
+{
+ public $errorcorrectionlevel;
+ public $margin;
+
+ function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 1)
+ {
+ if (!is_bool($verifyssl))
+ throw new Exception('VerifySSL must be bool');
+
+ $this->verifyssl = $verifyssl;
+
+ $this->errorcorrectionlevel = $errorcorrectionlevel;
+ $this->margin = $margin;
+ }
+
+ public function getMimeType()
+ {
+ return 'image/png';
+ }
+
+ public function getQRCodeImage($qrtext, $size)
+ {
+ return $this->getContent($this->getUrl($qrtext, $size));
+ }
+
+ public function getUrl($qrtext, $size)
+ {
+ return 'https://chart.googleapis.com/chart?cht=qr'
+ . '&chs=' . $size . 'x' . $size
+ . '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
+ . '&chl=' . rawurlencode($qrtext);
+ }
+} \ No newline at end of file
diff --git a/RobThree/TwoFactorAuth/Providers/IQRCodeProvider.php b/RobThree/TwoFactorAuth/Providers/IQRCodeProvider.php
new file mode 100644
index 0000000..6aeb856
--- /dev/null
+++ b/RobThree/TwoFactorAuth/Providers/IQRCodeProvider.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace RobThree\TwoFactorAuth\Providers;
+
+interface IQRCodeProvider
+{
+ public function getQRCodeImage($qrtext, $size);
+ public function getMimeType();
+} \ No newline at end of file
diff --git a/RobThree/TwoFactorAuth/Providers/QRServerProvider.php b/RobThree/TwoFactorAuth/Providers/QRServerProvider.php
new file mode 100644
index 0000000..d95260a
--- /dev/null
+++ b/RobThree/TwoFactorAuth/Providers/QRServerProvider.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace RobThree\TwoFactorAuth\Providers;
+
+// http://goqr.me/api/doc/create-qr-code/
+class QRServerProvider extends BaseHTTPQRCodeProvider
+{
+ public $errorcorrectionlevel;
+ public $margin;
+ public $qzone;
+ public $bgcolor;
+ public $color;
+ public $format;
+
+ function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 4, $qzone = 1, $bgcolor = 'ffffff', $color = '000000', $format = 'png')
+ {
+ if (!is_bool($verifyssl))
+ throw new Exception('VerifySSL must be bool');
+
+ $this->verifyssl = $verifyssl;
+
+ $this->errorcorrectionlevel = $errorcorrectionlevel;
+ $this->margin = $margin;
+ $this->qzone = $qzone;
+ $this->bgcolor = $bgcolor;
+ $this->color = $color;
+ $this->format = $format;
+ }
+
+ public function getMimeType()
+ {
+ switch (strtolower($this->format))
+ {
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ case 'jpg':
+ case 'jpeg':
+ return 'image/jpeg';
+ case 'svg':
+ return 'image/svg+xml';
+ case 'eps':
+ return 'application/postscript';
+ }
+ }
+
+ public function getQRCodeImage($qrtext, $size)
+ {
+ return $this->getContent($this->getUrl($qrtext, $size));
+ }
+
+ private function decodeColor($value)
+ {
+ return vsprintf('%d-%d-%d', sscanf($value, "%02x%02x%02x"));
+ }
+
+ public function getUrl($qrtext, $size)
+ {
+ return 'https://api.qrserver.com/v1/create-qr-code/'
+ . '?size=' . $size . 'x' . $size
+ . '&ecc=' . strtoupper($this->errorcorrectionlevel)
+ . '&margin=' . $this->margin
+ . '&qzone=' . $this->qzone
+ . '&bgcolor=' . $this->decodeColor($this->bgcolor)
+ . '&color=' . $this->decodeColor($this->color)
+ . '&format=' . strtolower($this->format)
+ . '&data=' . rawurlencode($qrtext);
+ }
+} \ No newline at end of file
diff --git a/RobThree/TwoFactorAuth/Providers/QRicketProvider.php b/RobThree/TwoFactorAuth/Providers/QRicketProvider.php
new file mode 100644
index 0000000..2356235
--- /dev/null
+++ b/RobThree/TwoFactorAuth/Providers/QRicketProvider.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace RobThree\TwoFactorAuth\Providers;
+
+// http://qrickit.com/qrickit_apps/qrickit_api.php
+class QRicketProvider extends BaseHTTPQRCodeProvider
+{
+ public $errorcorrectionlevel;
+ public $margin;
+ public $qzone;
+ public $bgcolor;
+ public $color;
+ public $format;
+
+ function __construct($errorcorrectionlevel = 'L', $bgcolor = 'ffffff', $color = '000000', $format = 'p')
+ {
+ $this->verifyssl = false;
+
+ $this->errorcorrectionlevel = $errorcorrectionlevel;
+ $this->bgcolor = $bgcolor;
+ $this->color = $color;
+ $this->format = $format;
+ }
+
+ public function getMimeType()
+ {
+ switch (strtolower($this->format))
+ {
+ case 'p':
+ return 'image/png';
+ case 'g':
+ return 'image/gif';
+ case 'j':
+ return 'image/jpeg';
+ }
+ }
+
+ public function getQRCodeImage($qrtext, $size)
+ {
+ return $this->getContent($this->getUrl($qrtext, $size));
+ }
+
+ public function getUrl($qrtext, $size)
+ {
+ return 'http://qrickit.com/api/qr'
+ . '?qrsize=' . $size
+ . '&e=' . strtolower($this->errorcorrectionlevel)
+ . '&bgdcolor=' . $this->bgcolor
+ . '&fgdcolor=' . $this->color
+ . '&t=' . strtolower($this->format)
+ . '&d=' . rawurlencode($qrtext);
+ }
+} \ No newline at end of file
diff --git a/RobThree/TwoFactorAuth/TwoFactorAuth.php b/RobThree/TwoFactorAuth/TwoFactorAuth.php
new file mode 100644
index 0000000..a825d46
--- /dev/null
+++ b/RobThree/TwoFactorAuth/TwoFactorAuth.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace RobThree\TwoFactorAuth;
+
+// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
+// Algorithms, digits, period etc. explained: https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
+class TwoFactorAuth
+{
+ private $algorithm;
+ private $period;
+ private $digits;
+ private $issuer;
+ private $qrcodeprovider;
+ private static $_base32;
+ private static $_base32lookup = array();
+ private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
+
+ function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1', $qrcodeprovider = null)
+ {
+ $this->issuer = $issuer;
+
+ if (!is_int($digits) || $digits <= 0)
+ throw new Exception('Digits must be int > 0');
+ $this->digits = $digits;
+
+ if (!is_int($period) || $period <= 0)
+ throw new Exception('Period must be int > 0');
+ $this->period = $period;
+
+ $algorithm = strtolower(trim($algorithm));
+ if (!in_array($algorithm, self::$_supportedalgos))
+ throw new Exception('Unsupported algorithm: ' . $algorithm);
+ $this->algorithm = $algorithm;
+
+ if ($qrcodeprovider==null)
+ $qrcodeprovider = new Providers\GoogleQRCodeProvider();
+
+ if (!($qrcodeprovider instanceof Providers\IQRCodeProvider))
+ throw new Exception('QRCodeProvider must implement IQRCodeProvider');
+
+ $this->qrcodeprovider = $qrcodeprovider;
+
+ self::$_base32 = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=');
+ self::$_base32lookup = array_flip(self::$_base32);
+ }
+
+ /**
+ * Create a new secret
+ */
+ public function createSecret($bits = 80)
+ {
+ $secret = '';
+ $bytes = ceil($bits / 5); //We use 5 bits of each byte
+ $rnd = openssl_random_pseudo_bytes($bytes);
+ for ($i = 0; $i < $bytes; $i++)
+ $secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
+ return $secret;
+ }
+
+ /**
+ * Calculate the code with given secret and point in time
+ */
+ public function getCode($secret, $time = null)
+ {
+ $secretkey = $this->base32Decode($secret);
+
+ $ts = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
+ $hm = hash_hmac($this->algorithm, $ts, $secretkey, true); // Hash it with users secret key
+ $hashpart = substr($hm, ord(substr($hm, -1)) & 0x0F, 4); // Use last nibble of result as index/offset and grab 4 bytes of the result
+ $value = unpack('N', $hashpart); // Unpack binary value
+ $value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
+
+ return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
+ */
+ public function verifyCode($secret, $code, $discrepancy = 1, $time = null)
+ {
+ $t = $this->getTime($time);
+ for ($i = -$discrepancy; $i <= $discrepancy; $i++)
+ {
+ if (strcmp($this->getCode($secret, $t + ($i * $this->period)), $code) === 0)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get data-uri of QRCode
+ */
+ public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
+ {
+ if (!is_int($size) || $size < 0)
+ throw new Exception('Size must be int > 0');
+
+ return 'data:'
+ . $this->qrcodeprovider->getMimeType()
+ . ';base64,'
+ . base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
+ }
+
+ private function getTime($time)
+ {
+ return ($time === null) ? time() : $time;
+ }
+
+ private function getTimeSlice($time = null, $offset = 0)
+ {
+ return (int)floor($time / $this->period) + ($offset * $this->period);
+ }
+
+ /**
+ * Builds a string to be encoded in a QR code
+ */
+ private function getQRText($label, $secret)
+ {
+ return 'otpauth://totp/' . rawurlencode($label)
+ . '?secret=' . rawurlencode($secret)
+ . '&issuer=' . rawurlencode($this->issuer)
+ . '&period=' . intval($this->period)
+ . '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
+ . '&digits=' . intval($this->digits);
+ }
+
+ private function base32Decode($value)
+ {
+ if (strlen($value)==0) return '';
+
+ $s = '';
+ foreach (str_split($value) as $c)
+ {
+ if ($c !== '=')
+ $s .= str_pad(decbin(self::$_base32lookup[$c]), 5, 0, STR_PAD_LEFT);
+ }
+ $l = strlen($s);
+ $r = trim(chunk_split(substr($s, 0, $l - ($l % 8)), 8, ' '));
+
+ $o = '';
+ foreach (explode(' ', $r) as $b)
+ $o .= chr(bindec(str_pad($b, 8, 0, STR_PAD_RIGHT)));
+
+ return $o;
+ }
+} \ No newline at end of file
diff --git a/TwoFactorAuth.phpproj b/TwoFactorAuth.phpproj
index 9d36825..ca8028e 100644
--- a/TwoFactorAuth.phpproj
+++ b/TwoFactorAuth.phpproj
@@ -23,12 +23,20 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="demo.php" />
- <Compile Include="src\TwoFactorAuth.php" />
+ <Compile Include="loader.php" />
+ <Compile Include="RobThree\TwoFactorAuth\Providers\BaseHTTPQRCodeProvider.php" />
+ <Compile Include="RobThree\TwoFactorAuth\Providers\GoogleQRCodeProvider.php" />
+ <Compile Include="RobThree\TwoFactorAuth\Providers\IQRCodeProvider.php" />
+ <Compile Include="RobThree\TwoFactorAuth\Providers\QRicketProvider.php" />
+ <Compile Include="RobThree\TwoFactorAuth\Providers\QRServerProvider.php" />
+ <Compile Include="RobThree\TwoFactorAuth\TwoFactorAuth.php" />
<Compile Include=".gitignore" />
<Compile Include="README.md" />
</ItemGroup>
<ItemGroup>
- <Folder Include="src\" />
+ <Folder Include="RobThree\" />
+ <Folder Include="RobThree\TwoFactorAuth\" />
+ <Folder Include="RobThree\TwoFactorAuth\Providers\" />
</ItemGroup>
<ItemGroup>
<Content Include="LICENSE" />
diff --git a/demo.php b/demo.php
index ba64627..e8c74cf 100644
--- a/demo.php
+++ b/demo.php
@@ -7,9 +7,12 @@
<ol>
<?php
error_reporting(-1);
- require_once 'src/TwoFactorAuth.php';
-
- $tfa = new TwoFactorAuth('MyApp');
+ require_once 'loader.php';
+ Loader::register('RobThree','RobThree');
+
+ use RobThree\TwoFactorAuth;
+
+ $tfa = new TwoFactorAuth\TwoFactorAuth('MyApp', 6, 30, 'sha1', new TwoFactorAuth\Providers\QRicketProvider());
echo '<li>First create a secret and associate it with a user';
$secret = $tfa->createSecret();
diff --git a/loader.php b/loader.php
new file mode 100644
index 0000000..208f24d
--- /dev/null
+++ b/loader.php
@@ -0,0 +1,50 @@
+<?php
+
+//http://www.leaseweblabs.com/2014/04/psr-0-psr-4-autoloading-classes-php/
+class Loader
+{
+ protected static $parentPath = null;
+ protected static $paths = null;
+ protected static $files = null;
+ protected static $nsChar = '\\';
+ protected static $initialized = false;
+
+ protected static function initialize()
+ {
+ if (static::$initialized) return;
+ static::$initialized = true;
+ static::$parentPath = __FILE__;
+ for ($i=substr_count(get_class(), static::$nsChar);$i>=0;$i--) {
+ static::$parentPath = dirname(static::$parentPath);
+ }
+ static::$paths = array();
+ static::$files = array(__FILE__);
+ }
+
+ public static function register($path,$namespace) {
+ if (!static::$initialized) static::initialize();
+ static::$paths[$namespace] = trim($path,DIRECTORY_SEPARATOR);
+ }
+
+ public static function load($class) {
+ if (class_exists($class,false)) return;
+ if (!static::$initialized) static::initialize();
+
+ foreach (static::$paths as $namespace => $path) {
+ if (!$namespace || $namespace.static::$nsChar === substr($class, 0, strlen($namespace.static::$nsChar))) {
+
+ $fileName = substr($class,strlen($namespace.static::$nsChar)-1);
+ $fileName = str_replace(static::$nsChar, DIRECTORY_SEPARATOR, ltrim($fileName,static::$nsChar));
+ $fileName = static::$parentPath.DIRECTORY_SEPARATOR.$path.DIRECTORY_SEPARATOR.$fileName.'.php';
+
+ if (file_exists($fileName)) {
+ include $fileName;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
+spl_autoload_register(array('Loader', 'load')); \ No newline at end of file
diff --git a/src/TwoFactorAuth.php b/src/TwoFactorAuth.php
deleted file mode 100644
index 6cffa6d..0000000
--- a/src/TwoFactorAuth.php
+++ /dev/null
@@ -1,329 +0,0 @@
-<?php
-// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
-// Algorithms, digits, period etc. explained: https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
-class TwoFactorAuth
-{
- private $algorithm;
- private $period;
- private $digits;
- private $issuer;
- private $qrcodeprovider;
- private static $_base32;
- private static $_base32lookup = array();
- private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
-
- function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1', $qrcodeprovider = null)
- {
- $this->issuer = $issuer;
-
- if (!is_int($digits) || $digits <= 0)
- throw new Exception('Digits must be int > 0');
- $this->digits = $digits;
-
- if (!is_int($period) || $period <= 0)
- throw new Exception('Period must be int > 0');
- $this->period = $period;
-
- $algorithm = strtolower(trim($algorithm));
- if (!in_array($algorithm, self::$_supportedalgos))
- throw new Exception('Unsupported algorithm: ' . $algorithm);
- $this->algorithm = $algorithm;
-
- if ($qrcodeprovider==null)
- $qrcodeprovider = new GoogleQRCodeProvider();
-
- if (!($qrcodeprovider instanceof IQRCodeProvider))
- throw new Exception('QRCodeProvider must implement IQRCodeProvider');
-
- $this->qrcodeprovider = $qrcodeprovider;
-
- self::$_base32 = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=');
- self::$_base32lookup = array_flip(self::$_base32);
- }
-
- /**
- * Create a new secret
- */
- public function createSecret($bits = 80)
- {
- $secret = '';
- $bytes = ceil($bits / 5); //We use 5 bits of each byte
- $rnd = openssl_random_pseudo_bytes($bytes);
- for ($i = 0; $i < $bytes; $i++)
- $secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
- return $secret;
- }
-
- /**
- * Calculate the code with given secret and point in time
- */
- public function getCode($secret, $time = null)
- {
- $secretkey = $this->base32Decode($secret);
-
- $ts = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
- $hm = hash_hmac($this->algorithm, $ts, $secretkey, true); // Hash it with users secret key
- $hashpart = substr($hm, ord(substr($hm, -1)) & 0x0F, 4); // Use last nibble of result as index/offset and grab 4 bytes of the result
- $value = unpack('N', $hashpart); // Unpack binary value
- $value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
-
- return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
- }
-
- /**
- * Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
- */
- public function verifyCode($secret, $code, $discrepancy = 1, $time = null)
- {
- $t = $this->getTime($time);
- for ($i = -$discrepancy; $i <= $discrepancy; $i++)
- {
- if (strcmp($this->getCode($secret, $t + ($i * $this->period)), $code) === 0)
- return true;
- }
-
- return false;
- }
-
- /**
- * Get data-uri of QRCode
- */
- public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
- {
- if (!is_int($size) || $size < 0)
- throw new Exception('Size must be int > 0');
-
- return 'data:'
- . $this->qrcodeprovider->getMimeType()
- . ';base64,'
- . base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
- }
-
- private function getTime($time)
- {
- return ($time === null) ? time() : $time;
- }
-
- private function getTimeSlice($time = null, $offset = 0)
- {
- return (int)floor($time / $this->period) + ($offset * $this->period);
- }
-
- /**
- * Builds a string to be encoded in a QR code
- */
- private function getQRText($label, $secret)
- {
- return 'otpauth://totp/' . rawurlencode($label)
- . '?secret=' . rawurlencode($secret)
- . '&issuer=' . rawurlencode($this->issuer)
- . '&period=' . intval($this->period)
- . '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
- . '&digits=' . intval($this->digits);
- }
-
- private function base32Decode($value)
- {
- if (strlen($value)==0) return '';
-
- $s = '';
- foreach (str_split($value) as $c)
- {
- if ($c !== '=')
- $s .= str_pad(decbin(self::$_base32lookup[$c]), 5, 0, STR_PAD_LEFT);
- }
- $l = strlen($s);
- $r = trim(chunk_split(substr($s, 0, $l - ($l % 8)), 8, ' '));
-
- $o = '';
- foreach (explode(' ', $r) as $b)
- $o .= chr(bindec(str_pad($b, 8, 0, STR_PAD_RIGHT)));
-
- return $o;
- }
-}
-
-interface IQRCodeProvider
-{
- public function getQRCodeImage($qrtext, $size);
- public function getMimeType();
-}
-
-abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
-{
- protected $verifyssl;
-
- protected function getContent($url)
- {
- $ch = curl_init();
-
- curl_setopt_array($ch, array(
- CURLOPT_URL => $url,
- CURLOPT_FOLLOWLOCATION => true,
- CURLOPT_MAXREDIRS => 3,
- CURLOPT_RETURNTRANSFER => true,
- CURLOPT_CONNECTTIMEOUT => 10,
- CURLOPT_DNS_CACHE_TIMEOUT => 10,
- CURLOPT_TIMEOUT => 10,
- CURLOPT_SSL_VERIFYPEER => $this->verifyssl,
- CURLOPT_USERAGENT => 'TwoFactorAuth'
- ));
- $data = curl_exec($ch);
-
- curl_close($ch);
- return $data;
- }
-}
-
-// https://developers.google.com/chart/infographics/docs/qr_codes
-class GoogleQRCodeProvider extends BaseHTTPQRCodeProvider
-{
- public $errorcorrectionlevel;
- public $margin;
-
- function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 1)
- {
- if (!is_bool($verifyssl))
- throw new Exception('VerifySSL must be bool');
-
- $this->verifyssl = $verifyssl;
-
- $this->errorcorrectionlevel = $errorcorrectionlevel;
- $this->margin = $margin;
- }
-
- public function getMimeType()
- {
- return 'image/png';
- }
-
- public function getQRCodeImage($qrtext, $size)
- {
- return $this->getContent($this->getUrl($qrtext, $size));
- }
-
- public function getUrl($qrtext, $size)
- {
- return 'https://chart.googleapis.com/chart?cht=qr'
- . '&chs=' . $size . 'x' . $size
- . '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
- . '&chl=' . rawurlencode($qrtext);
- }
-}
-
-// http://goqr.me/api/doc/create-qr-code/
-class QRServerProvider extends BaseHTTPQRCodeProvider
-{
- public $errorcorrectionlevel;
- public $margin;
- public $qzone;
- public $bgcolor;
- public $color;
- public $format;
-
- function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 4, $qzone = 1, $bgcolor = 'ffffff', $color = '000000', $format = 'png')
- {
- if (!is_bool($verifyssl))
- throw new Exception('VerifySSL must be bool');
-
- $this->verifyssl = $verifyssl;
-
- $this->errorcorrectionlevel = $errorcorrectionlevel;
- $this->margin = $margin;
- $this->qzone = $qzone;
- $this->bgcolor = $bgcolor;
- $this->color = $color;
- $this->format = $format;
- }
-
- public function getMimeType()
- {
- switch (strtolower($this->format))
- {
- case 'png':
- return 'image/png';
- case 'gif':
- return 'image/gif';
- case 'jpg':
- case 'jpeg':
- return 'image/jpeg';
- case 'svg':
- return 'image/svg+xml';
- case 'eps':
- return 'application/postscript';
- }
- }
-
- public function getQRCodeImage($qrtext, $size)
- {
- return $this->getContent($this->getUrl($qrtext, $size));
- }
-
- private function decodeColor($value)
- {
- return vsprintf('%d-%d-%d', sscanf($value, "%02x%02x%02x"));
- }
-
- public function getUrl($qrtext, $size)
- {
- return 'https://api.qrserver.com/v1/create-qr-code/'
- . '?size=' . $size . 'x' . $size
- . '&ecc=' . strtoupper($this->errorcorrectionlevel)
- . '&margin=' . $this->margin
- . '&qzone=' . $this->qzone
- . '&bgcolor=' . $this->decodeColor($this->bgcolor)
- . '&color=' . $this->decodeColor($this->color)
- . '&format=' . strtolower($this->format)
- . '&data=' . rawurlencode($qrtext);
- }
-}
-
-// http://qrickit.com/qrickit_apps/qrickit_api.php
-class QRicketProvider extends BaseHTTPQRCodeProvider
-{
- public $errorcorrectionlevel;
- public $margin;
- public $qzone;
- public $bgcolor;
- public $color;
- public $format;
-
- function __construct($errorcorrectionlevel = 'L', $bgcolor = 'ffffff', $color = '000000', $format = 'p')
- {
- $this->verifyssl = false;
-
- $this->errorcorrectionlevel = $errorcorrectionlevel;
- $this->bgcolor = $bgcolor;
- $this->color = $color;
- $this->format = $format;
- }
-
- public function getMimeType()
- {
- switch (strtolower($this->format))
- {
- case 'p':
- return 'image/png';
- case 'g':
- return 'image/gif';
- case 'j':
- return 'image/jpeg';
- }
- }
-
- public function getQRCodeImage($qrtext, $size)
- {
- return $this->getContent($this->getUrl($qrtext, $size));
- }
-
- public function getUrl($qrtext, $size)
- {
- return 'http://qrickit.com/api/qr'
- . '?qrsize=' . $size
- . '&e=' . strtolower($this->errorcorrectionlevel)
- . '&bgdcolor=' . $this->bgcolor
- . '&fgdcolor=' . $this->color
- . '&t=' . strtolower($this->format)
- . '&d=' . rawurlencode($qrtext);
- }
-} \ No newline at end of file