diff options
Diffstat (limited to 'vendor/christian-riesen/otp')
-rw-r--r-- | vendor/christian-riesen/otp/.gitignore | 5 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/.travis.yml | 11 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/LICENSE | 20 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/README.md | 105 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/composer.json | 28 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/example/index.php | 100 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/phpunit.xml.dist | 15 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php | 189 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/src/Otp/Otp.php | 310 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/src/Otp/OtpInterface.php | 65 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/tests/Otp/GoogleAuthenticatorTest.php | 88 | ||||
-rw-r--r-- | vendor/christian-riesen/otp/tests/Otp/OtpTest.php | 124 |
12 files changed, 1060 insertions, 0 deletions
diff --git a/vendor/christian-riesen/otp/.gitignore b/vendor/christian-riesen/otp/.gitignore new file mode 100644 index 00000000..bab62331 --- /dev/null +++ b/vendor/christian-riesen/otp/.gitignore @@ -0,0 +1,5 @@ +.svn +/.buildpath +/.project +/.settings + diff --git a/vendor/christian-riesen/otp/.travis.yml b/vendor/christian-riesen/otp/.travis.yml new file mode 100644 index 00000000..b863299e --- /dev/null +++ b/vendor/christian-riesen/otp/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7.0 + +script: phpunit + diff --git a/vendor/christian-riesen/otp/LICENSE b/vendor/christian-riesen/otp/LICENSE new file mode 100644 index 00000000..70894746 --- /dev/null +++ b/vendor/christian-riesen/otp/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) Christian Riesen http://christianriesen.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/christian-riesen/otp/README.md b/vendor/christian-riesen/otp/README.md new file mode 100644 index 00000000..c979dbbc --- /dev/null +++ b/vendor/christian-riesen/otp/README.md @@ -0,0 +1,105 @@ +One Time Passwords +================== + +[![Build Status](https://secure.travis-ci.org/ChristianRiesen/otp.png)](http://travis-ci.org/ChristianRiesen/otp) + +Did you like this? Flattr it: + +[![Flattr otp](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/719284/ChristianRiesenotp-on-GitHub) + +Installation +------------ + +Use [composer](http://getcomposer.org/) and require the library in your `composer.json` + + { + "require": { + "christian-riesen/otp": "1.*", + } + } + +Usage +----- + +```php +<?php + +use Otp\Otp; +use Otp\GoogleAuthenticator; + +// Seperate class, see https://github.com/ChristianRiesen/base32 +use Base32\Base32; + +// Get a Pseudo Secret +// Defaults to 16 characters +$secret = GoogleAuthenticator::generateRandom(); + +// Url for the QR code +// Using totp method +$url = GoogleAuthenticator::getQrCodeUrl('totp', 'Label like user@host.com', $secret); + +// Save the secret with the users account +// Display QR Code to the user + +// Now how to check +$otp = new Otp(); + +// $key is a 6 digit number, coming from the User +// Assuming this is present and sanitized +// Allows for a 1 code time drift by default +// Third parameter can alter that behavior +if ($otp->checkTotp(Base32::decode($secret), $key)) { + // Correct key + // IMPORTANT! Note this key as being used + // so nobody could launch a replay attack. + // Cache that for the next minutes and you + // should be good. +} else { + // Wrong key +} + +// Just to create a key for display (testing) +$key = $otp->totp($secret); + +``` + +Sample script in `example` folder. Requires sessions to work (for secret storage). + +Class Otp +--------- + +Implements hotp according to [RFC4226](https://tools.ietf.org/html/rfc4226) and totp according to [RFC6238](https://tools.ietf.org/html/rfc6238) (only sha1 algorithm). Once you have a secret, you can use it directly in this class to create the passwords themselves (mainly for debugging use) or use the check functions to safely check the validity of the keys. The `checkTotp` function also includes a helper to battle timedrift. + +Class GoogleAuthenticator +------------------------- + +Static function class to generate a correct url for the QR code, so you can easy scan it with your device. Google Authenticator is avaiaible as application for iPhone and Android. This removes the burden to create such an app from the developers of websites by using this set of classes. + +There are also older open source versions of the Google Authenticator app for both [iPhone](https://github.com/google/google-authenticator) and [Android](https://github.com/google/google-authenticator-android) + +This helper class uses the random_int function from PHP7, or the polyfill method from [paragonie/random_compat](https://packagist.org/packages/paragonie/random_compat) if present and falls back on other (less "secure") random generators. + +About +===== + +Requirements +------------ + +PHP 5.3.x+ + +Uses [Base32 class](https://github.com/ChristianRiesen/base32). + +If you want to run the tests, PHPUnit 3.6 or up is required. + +Author +------ + +Christian Riesen <chris.riesen@gmail.com> http://christianriesen.com + +Acknowledgements +---------------- + +The classes have been inspired by many different places that were talking about otp and Google Authenticator. Thank you all for your help. + +Project setup ideas blantently taken from https://github.com/Seldaek/monolog + diff --git a/vendor/christian-riesen/otp/composer.json b/vendor/christian-riesen/otp/composer.json new file mode 100644 index 00000000..94f16c67 --- /dev/null +++ b/vendor/christian-riesen/otp/composer.json @@ -0,0 +1,28 @@ +{ + "name": "christian-riesen/otp", + "type": "library", + "description": "One Time Passwords, hotp and totp according to RFC4226 and RFC6238", + "keywords": ["otp","hotp","totp","googleauthenticator","rfc4226","rfc6238"], + "homepage": "https://github.com/ChristianRiesen/otp", + "license": "MIT", + "authors": [ + { + "name": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "christian-riesen/base32": ">=1.0" + }, + "suggest": { + "paragonie/random_compat": "Optional polyfill for a more secure random generator for pre PHP7 versions" + }, + "autoload": { + "psr-0": { + "Otp": "src" + } + } +} diff --git a/vendor/christian-riesen/otp/example/index.php b/vendor/christian-riesen/otp/example/index.php new file mode 100644 index 00000000..4da362c5 --- /dev/null +++ b/vendor/christian-riesen/otp/example/index.php @@ -0,0 +1,100 @@ +<?php + +session_start(); // using it as storage temporary + +require_once __DIR__ . '/../vendor/autoload.php'; + +use Otp\Otp; +use Otp\GoogleAuthenticator; +use Base32\Base32; + +// Getting a secret, either by generating or from storage +// DON'T use sessions as storage for this in production!!! +$secret = 0; + +if (isset($_SESSION['otpsecret'])) { + $secret = $_SESSION['otpsecret']; +} + +if (strlen($secret) != 16) { + $secret = GoogleAuthenticator::generateRandom(); + $_SESSION['otpsecret'] = $secret; +} + +// The secret is now an easy stored Base32 string. +// To use it in totp though we need to decode it into the original +$otp = new Otp(); + +$currentTotp = $otp->totp(Base32::decode($secret)); + +$qrCode = GoogleAuthenticator::getQrCodeUrl('totp', 'otpsample@cr', $secret); +$keyUri = GoogleAuthenticator::getKeyUri('totp', 'otpsample@cr', $secret); + +?><html> +<head> +<title>One Time Passwords Example</title> +</head> +<body> + +<h1>One Time Passwords Example</h1> + +Secret is <?php echo $secret; ?>. This is saved with the users credentials. +<br /> +<br /> +<hr /> + +QR Code for totp:<br /> +<img src="<?php echo $qrCode; ?>" /> +<br /> +This QR Code contains the Key URI: <?php echo $keyUri; ?> +<br /> +<hr /> + +Current totp would be <?php echo $currentTotp; ?><br /> +<br /> +<hr /> + +Because of timedrift, you could technically enter a code before or after it +would actually be used. This form uses the checkTotp function. To test this, +open this page, wait until the key changes once or twice (not more) on your +Google Authenticator, then hit submit. Even though the key is "wrong" because of +small time differences, you can still use it. +<form action="" method="post"> +<input type="text" name="otpkey" value="<?php echo $currentTotp; ?>" /><br /> +<input type="submit"> +</form> + +<br /> +Output:<br /> +<br /> + + +<?php + +if (isset($_POST['otpkey'])) { + // Sanatizing, this should take care of it + $key = preg_replace('/[^0-9]/', '', $_POST['otpkey']); + + // Standard is 6 for keys, but can be changed with setDigits on $otp + if (strlen($key) == 6) { + // Remember that the secret is a base32 string that needs decoding + // to use it here! + if ($otp->checkTotp(Base32::decode($secret), $key)) { + echo 'Key correct!'; + // Add here something that makes note of this key and will not allow + // the use of it, for this user for the next 2 minutes. This way you + // prevent a replay attack. Otherwise your OTP is missing one of the + // key features it can bring in security to your application! + } else { + echo 'Wrong key!'; + } + + } else { + echo 'Key not the correct size'; + } +} + +?> + +</body> +</html> diff --git a/vendor/christian-riesen/otp/phpunit.xml.dist b/vendor/christian-riesen/otp/phpunit.xml.dist new file mode 100644 index 00000000..7ffc6c63 --- /dev/null +++ b/vendor/christian-riesen/otp/phpunit.xml.dist @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<phpunit colors="true"> + <testsuites> + <testsuite name="Otp Test Suite"> + <directory>tests/Otp/</directory> + </testsuite> + </testsuites> + + <filter> + <whitelist> + <directory suffix=".php">src/Otp/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php b/vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php new file mode 100644 index 00000000..23e67ff0 --- /dev/null +++ b/vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php @@ -0,0 +1,189 @@ +<?php +namespace Otp; + +/** + * Google Authenticator + * + * Last update: 2014-08-19 + * + * Can be easy used with Google Authenticator + * @link https://code.google.com/p/google-authenticator/ + * + * @author Christian Riesen <chris.riesen@gmail.com> + * @link http://christianriesen.com + * @license MIT License see LICENSE file + */ + +class GoogleAuthenticator +{ + protected static $allowedTypes = array('hotp', 'totp'); + + protected static $height = 200; + protected static $width = 200; + + /** + * Returns the Key URI + * + * Format of encoded url is here: + * https://code.google.com/p/google-authenticator/wiki/KeyUriFormat + * Should be done in a better fashion + * + * @param string $type totp or hotp + * @param string $label Label to display this as to the user + * @param string $secret Base32 encoded secret + * @param integer $counter Required by hotp, otherwise ignored + * @param array $options Optional fields that will be set if present + * + * @return string Key URI + */ + public static function getKeyUri($type, $label, $secret, $counter = null, $options = array()) + { + // two types only.. + if (!in_array($type, self::$allowedTypes)) { + throw new \InvalidArgumentException('Type has to be of allowed types list'); + } + + // Label can't be empty + $label = trim($label); + + if (strlen($label) < 1) { + throw new \InvalidArgumentException('Label has to be one or more printable characters'); + } + + if (substr_count($label, ':') > 2) { + throw new \InvalidArgumentException('Account name contains illegal colon characters'); + } + + // Secret needs to be here + if (strlen($secret) < 1) { + throw new \InvalidArgumentException('No secret present'); + } + + // check for counter on hotp + if ($type == 'hotp' && is_null($counter)) { + throw new \InvalidArgumentException('Counter required for hotp'); + } + + // This is the base, these are at least required + $otpauth = 'otpauth://' . $type . '/' . str_replace(array(':', ' '), array('%3A', '%20'), $label) . '?secret=' . rawurlencode($secret); + + if ($type == 'hotp' && !is_null($counter)) { + $otpauth .= '&counter=' . intval($counter); + } + + // Now check the options array + + // algorithm (currently ignored by Authenticator) + // Defaults to SHA1 + if (array_key_exists('algorithm', $options)) { + $otpauth .= '&algorithm=' . rawurlencode($options['algorithm']); + } + + // digits (currently ignored by Authenticator) + // Defaults to 6 + if (array_key_exists('digits', $options) && intval($options['digits']) !== 6 && intval($options['digits']) !== 8) { + throw new \InvalidArgumentException('Digits can only have the values 6 or 8, ' . $options['digits'] . ' given'); + } elseif (array_key_exists('digits', $options)) { + $otpauth .= '&digits=' . intval($options['digits']); + } + + // period, only for totp (currently ignored by Authenticator) + // Defaults to 30 + if ($type == 'totp' && array_key_exists('period', $options)) { + $otpauth .= '&period=' . rawurlencode($options['period']); + } + + // issuer + // Defaults to none + if (array_key_exists('issuer', $options)) { + $otpauth .= '&issuer=' . rawurlencode($options['issuer']); + } + + return $otpauth; + } + + + /** + * Returns the QR code url + * + * Format of encoded url is here: + * https://code.google.com/p/google-authenticator/wiki/KeyUriFormat + * Should be done in a better fashion + * + * @param string $type totp or hotp + * @param string $label Label to display this as to the user + * @param string $secret Base32 encoded secret + * @param integer $counter Required by hotp, otherwise ignored + * @param array $options Optional fields that will be set if present + * + * @return string URL to the QR code + */ + public static function getQrCodeUrl($type, $label, $secret, $counter = null, $options = array()) + { + // Width and height can be overwritten + $width = self::$width; + + if (array_key_exists('width', $options) && is_numeric($options['width'])) { + $width = $options['width']; + } + + $height = self::$height; + + if (array_key_exists('height', $options) && is_numeric($options['height'])) { + $height = $options['height']; + } + + $otpauth = self::getKeyUri($type, $label, $secret, $counter, $options); + + $url = 'https://chart.googleapis.com/chart?chs=' . $width . 'x' + . $height . '&cht=qr&chld=M|0&chl=' . urlencode($otpauth); + + return $url; + } + + /** + * Creates a pseudo random Base32 string + * + * This could decode into anything. It's located here as a small helper + * where code that might need base32 usually also needs something like this. + * + * @param integer $length Exact length of output string + * @return string Base32 encoded random + */ + public static function generateRandom($length = 16) + { + $keys = array_merge(range('A','Z'), range(2,7)); // No padding char + + $string = ''; + + for ($i = 0; $i < $length; $i++) { + $string .= $keys[self::getRand()]; + } + + return $string; + } + + /** + * Get random number + * + * @return int Random number between 0 and 31 (including) + */ + private static function getRand() + { + if (function_exists('random_int')) { + // Uses either the PHP7 internal function or the polyfill if present + return random_int(0, 31); + } elseif (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes(2); + $number = hexdec(bin2hex($bytes)); + + if ($number > 31) { + $number = $number % 32; + } + + return $number; + } else { + return mt_rand(0, 31); + } + } +} diff --git a/vendor/christian-riesen/otp/src/Otp/Otp.php b/vendor/christian-riesen/otp/src/Otp/Otp.php new file mode 100644 index 00000000..7d954870 --- /dev/null +++ b/vendor/christian-riesen/otp/src/Otp/Otp.php @@ -0,0 +1,310 @@ +<?php +namespace Otp; + +/** + * One Time Passwords + * + * Last update: 2012-06-16 + * + * Implements HOTP and TOTP + * + * HMAC-Based One-time Password(HOTP) algorithm specified in RFC 4226 + * @link https://tools.ietf.org/html/rfc4226 + * + * Time-based One-time Password (TOTP) algorithm specified in RFC 6238 + * @link https://tools.ietf.org/html/rfc6238 + * + * As a note: This code is NOT 2038 proof! The min concern is the function + * getBinaryCounter that uses the pack function which can't handle 64bit yet. + * + * Can be easy used with Google Authenticator + * @link https://code.google.com/p/google-authenticator/ + * + * @author Christian Riesen <chris.riesen@gmail.com> + * @link http://christianriesen.com + * @license MIT License see LICENSE file + */ + +class Otp implements OtpInterface +{ + /** + * The digits the code can have + * + * Either 6 or 8. + * Authenticator does only support 6. + * + * @var integer + */ + protected $digits = 6; + + /** + * Time in seconds one counter period is long + * + * @var integer + */ + protected $period = 30; + + /** + * Possible algorithms + * + * @var array + */ + protected $allowedAlgorithms = array('sha1', 'sha256', 'sha512'); + + /** + * Currently used algorithm + * + * @var string + */ + protected $algorithm = 'sha1'; + + /* (non-PHPdoc) + * @see Otp.OtpInterface::hotp() + */ + public function hotp($secret, $counter) + { + if (!is_numeric($counter)) { + throw new \InvalidArgumentException('Counter must be integer'); + } + + $hash = hash_hmac( + $this->algorithm, + $this->getBinaryCounter($counter), + $secret, + true + ); + + return str_pad($this->truncate($hash), $this->digits, '0', STR_PAD_LEFT); + } + + /* (non-PHPdoc) + * @see Otp.OtpInterface::totp() + */ + public function totp($secret, $timecounter = null) + { + if (is_null($timecounter)) { + $timecounter = $this->getTimecounter(); + } + + return $this->hotp($secret, $timecounter); + } + + /* (non-PHPdoc) + * @see Otp.OtpInterface::checkHotp() + */ + public function checkHotp($secret, $counter, $key) + { + return $this->safeCompare($this->hotp($secret, $counter), $key); + } + + /* (non-PHPdoc) + * @see Otp.OtpInterface::checkTotp() + */ + public function checkTotp($secret, $key, $timedrift = 1) + { + if (!is_numeric($timedrift) || $timedrift < 0) { + throw new \InvalidArgumentException('Invalid timedrift supplied'); + } + // Counter comes from time now + // Also we check the current timestamp as well as previous and future ones + // according to $timerange + $timecounter = $this->getTimecounter(); + + $start = $timecounter - ($timedrift); + $end = $timecounter + ($timedrift); + + // We first try the current, as it is the most likely to work + if ($this->safeCompare($this->totp($secret, $timecounter), $key)) { + return true; + } elseif ($timedrift == 0) { + // When timedrift is 0, this is the end of the checks + return false; + } + + // Well, that didn't work, so try the others + for ($t = $start; $t <= $end; $t = $t + 1) { + if ($t == $timecounter) { + // Already tried that one + continue; + } + + if ($this->safeCompare($this->totp($secret, $t), $key)) { + return true; + } + } + + // if none worked, then return false + return false; + } + + /** + * Changing the used algorithm for hashing + * + * Can only be one of the algorithms in the allowedAlgorithms property. + * + * @param string $algorithm + * @throws \InvalidArgumentException + * @return \Otp\Otp + */ + + /* + * This has been disabled since it does not bring the expected results + * according to the RFC test vectors for sha256 or sha512. + * Until that is fixed, the algorithm simply stays at sha1. + * Google Authenticator does not support sha256 and sha512 at the moment. + * + + public function setAlgorithm($algorithm) + { + if (!in_array($algorithm, $this->allowedAlgorithms)) { + throw new \InvalidArgumentException('Not an allowed algorithm: ' . $algorithm); + } + + $this->algorithm = $algorithm; + + return $this; + } + // */ + + /** + * Get the algorithms name (lowercase) + * + * @return string + */ + public function getAlgorithm() + { + return $this->algorithm; + } + + /** + * Setting period lenght for totp + * + * @param integer $period + * @throws \InvalidArgumentException + * @return \Otp\Otp + */ + public function setPeriod($period) + { + if (!is_int($period)) { + throw new \InvalidArgumentException('Period must be an integer'); + } + + $this->period = $period; + + return $this; + } + + /** + * Returns the set period value + * + * @return integer + */ + public function getPeriod() + { + return $this->period; + } + + /** + * Setting number of otp digits + * + * @param integer $digits Number of digits for the otp (6 or 8) + * @throws \InvalidArgumentException + * @return \Otp\Otp + */ + public function setDigits($digits) + { + if (!in_array($digits, array(6, 8))) { + throw new \InvalidArgumentException('Digits must be 6 or 8'); + } + + $this->digits = $digits; + + return $this; + } + + /** + * Returns number of digits in the otp + * + * @return integer + */ + public function getDigits() + { + return $this->digits; + } + + /** + * Generates a binary counter for hashing + * + * Warning: Not 2038 safe. Maybe until then pack supports 64bit. + * + * @param integer $counter Counter in integer form + * @return string Binary string + */ + protected function getBinaryCounter($counter) + { + return pack('N*', 0) . pack('N*', $counter); + } + + /** + * Generating time counter + * + * This is the time divided by 30 by default. + * + * @return integer Time counter + */ + protected function getTimecounter() + { + return floor(time() / $this->period); + } + + /** + * Creates the basic number for otp from hash + * + * This number is left padded with zeros to the required length by the + * calling function. + * + * @param string $hash hmac hash + * @return number + */ + protected function truncate($hash) + { + $offset = ord($hash[19]) & 0xf; + + return ( + ((ord($hash[$offset+0]) & 0x7f) << 24 ) | + ((ord($hash[$offset+1]) & 0xff) << 16 ) | + ((ord($hash[$offset+2]) & 0xff) << 8 ) | + (ord($hash[$offset+3]) & 0xff) + ) % pow(10, $this->digits); + } + + /** + * Safely compares two inputs + * + * Assumed inputs are numbers and strings. + * Compares them in a time linear manner. No matter how much you guess + * correct of the partial content, it does not change the time it takes to + * run the entire comparison. + * + * @param mixed $a + * @param mixed $b + * @return boolean + */ + protected function safeCompare($a, $b) + { + $sha1a = sha1($a); + $sha1b = sha1($b); + + // Now the compare is always the same length. Even considering minute + // time differences in sha1 creation, all you know is that a longer + // input takes longer to hash, not how long the actual compared value is + $result = 0; + + for ($i = 0; $i < 40; $i++) { + $result |= ord($sha1a[$i]) ^ ord($sha1b[$i]); + } + + return $result == 0; + } +} + diff --git a/vendor/christian-riesen/otp/src/Otp/OtpInterface.php b/vendor/christian-riesen/otp/src/Otp/OtpInterface.php new file mode 100644 index 00000000..7ff34f2f --- /dev/null +++ b/vendor/christian-riesen/otp/src/Otp/OtpInterface.php @@ -0,0 +1,65 @@ +<?php +namespace Otp; + +/** + * Interface for HOTP and TOTP + * + * Last update: 2012-06-16 + * + * HMAC-Based One-time Password(HOTP) algorithm specified in RFC 4226 + * @link https://tools.ietf.org/html/rfc4226 + * + * Time-based One-time Password (TOTP) algorithm specified in RFC 6238 + * @link https://tools.ietf.org/html/rfc6238 + * + * @author Christian Riesen <chris.riesen@gmail.com> + * @link http://christianriesen.com + * @license MIT License see LICENSE file + */ + +interface OtpInterface +{ + /** + * Returns OTP using the HOTP algorithm + * + * @param string $secret + * @param integer $counter + * @return string One Time Password + */ + function hotp($secret, $counter); + + /** + * Returns OTP using the TOTP algorithm + * + * @param string $secret + * @param integer $timecounter Optional: Uses current time if null + * @return string One Time Password + */ + function totp($secret, $timecounter = null); + + /** + * Checks Hotp against a key + * + * This is a helper function, but is here to ensure the Totp can be checked + * in the same manner. + * + * @param string $secret + * @param integer $counter + * @param string $key + * + * @return boolean If key is correct + */ + function checkHotp($secret, $counter, $key); + + /** + * Checks Totp agains a key + * + * + * @param string $secret + * @param integer $key + * @param integer $timedrift + * + * @return boolean If key is correct + */ + function checkTotp($secret, $key, $timedrift = 1); +} diff --git a/vendor/christian-riesen/otp/tests/Otp/GoogleAuthenticatorTest.php b/vendor/christian-riesen/otp/tests/Otp/GoogleAuthenticatorTest.php new file mode 100644 index 00000000..219bdaa8 --- /dev/null +++ b/vendor/christian-riesen/otp/tests/Otp/GoogleAuthenticatorTest.php @@ -0,0 +1,88 @@ +<?php + +require_once __DIR__ . '/../../src/Otp/GoogleAuthenticator.php'; + +use Otp\GoogleAuthenticator; + +/** + * GoogleAuthenticator test case. + */ +class GoogleAuthenticatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests getQrCodeUrl + */ + public function testGetQrCodeUrl() + { + $secret = 'MEP3EYVA6XNFNVNM'; // testing secret + + // Standard totp case + $this->assertEquals( + 'https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=M|0&chl=otpauth%3A%2F%2Ftotp%2Fuser%40host.com%3Fsecret%3DMEP3EYVA6XNFNVNM', + GoogleAuthenticator::getQrCodeUrl('totp', 'user@host.com', $secret) + ); + + // hotp (include a counter) + $this->assertEquals( + 'https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=M|0&chl=otpauth%3A%2F%2Fhotp%2Fuser%40host.com%3Fsecret%3DMEP3EYVA6XNFNVNM%26counter%3D1234', + GoogleAuthenticator::getQrCodeUrl('hotp', 'user@host.com', $secret, 1234) + ); + + // totp, this time with a parameter for chaning the size of the QR + $this->assertEquals( + 'https://chart.googleapis.com/chart?chs=300x300&cht=qr&chld=M|0&chl=otpauth%3A%2F%2Ftotp%2Fuser%40host.com%3Fsecret%3DMEP3EYVA6XNFNVNM', + GoogleAuthenticator::getQrCodeUrl('totp', 'user@host.com', $secret, null, array('height' => 300, 'width' => 300)) + ); + + } + + /** + * Tests getKeyUri + */ + public function testGetKeyUri() + { + $secret = 'MEP3EYVA6XNFNVNM'; // testing secret + + // Standard totp case + $this->assertEquals( + 'otpauth://totp/user@host.com?secret=MEP3EYVA6XNFNVNM', + GoogleAuthenticator::getKeyUri('totp', 'user@host.com', $secret) + ); + + // hotp (include a counter) + $this->assertEquals( + 'otpauth://hotp/user@host.com?secret=MEP3EYVA6XNFNVNM&counter=1234', + GoogleAuthenticator::getKeyUri('hotp', 'user@host.com', $secret, 1234) + ); + + // totp/hotp with an issuer in the label + $this->assertEquals( + 'otpauth://hotp/issuer%3Auser@host.com?secret=MEP3EYVA6XNFNVNM&counter=1234', + GoogleAuthenticator::getKeyUri('hotp', 'issuer:user@host.com', $secret, 1234) + ); + + // totp/hotp with an issuer and spaces in the label + $this->assertEquals( + 'otpauth://hotp/an%20issuer%3A%20user@host.com?secret=MEP3EYVA6XNFNVNM&counter=1234', + GoogleAuthenticator::getKeyUri('hotp', 'an issuer: user@host.com', $secret, 1234) + ); + + // totp/hotp with an issuer as option + $this->assertEquals( + 'otpauth://hotp/an%20issuer%3Auser@host.com?secret=MEP3EYVA6XNFNVNM&counter=1234&issuer=an%20issuer', + GoogleAuthenticator::getKeyUri('hotp', 'an issuer:user@host.com', $secret, 1234, array('issuer' => 'an issuer')) + ); + } + + /** + * Tests generateRandom + */ + public function testGenerateRandom() + { + // contains numbers 2-7 and letters A-Z in large letters, 16 chars long + $this->assertRegExp('/[2-7A-Z]{16}/', GoogleAuthenticator::generateRandom()); + + // Can be told to make a longer secret + $this->assertRegExp('/[2-7A-Z]{18}/', GoogleAuthenticator::generateRandom(18)); + } +} diff --git a/vendor/christian-riesen/otp/tests/Otp/OtpTest.php b/vendor/christian-riesen/otp/tests/Otp/OtpTest.php new file mode 100644 index 00000000..ccfdddab --- /dev/null +++ b/vendor/christian-riesen/otp/tests/Otp/OtpTest.php @@ -0,0 +1,124 @@ +<?php + +require_once __DIR__ . '/../../src/Otp/OtpInterface.php'; +require_once __DIR__ . '/../../src/Otp/Otp.php'; + +use Otp\Otp; + +/** + * Otp test case. + */ +class OtpTest extends \PHPUnit_Framework_TestCase +{ + /** + * + * @var Otp + */ + private $Otp; + + private $secret = "12345678901234567890"; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + + $this->Otp = new Otp(); + + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->Otp = null; + + parent::tearDown(); + } + + /** + * Tests Otp->hotp() + * + * Using test vectors from RFC + * https://tools.ietf.org/html/rfc4226 + */ + public function testHotpRfc() + { + $secret = $this->secret; + + $this->assertEquals('755224', $this->Otp->hotp($secret, 0)); + $this->assertEquals('287082', $this->Otp->hotp($secret, 1)); + $this->assertEquals('359152', $this->Otp->hotp($secret, 2)); + $this->assertEquals('969429', $this->Otp->hotp($secret, 3)); + $this->assertEquals('338314', $this->Otp->hotp($secret, 4)); + $this->assertEquals('254676', $this->Otp->hotp($secret, 5)); + $this->assertEquals('287922', $this->Otp->hotp($secret, 6)); + $this->assertEquals('162583', $this->Otp->hotp($secret, 7)); + $this->assertEquals('399871', $this->Otp->hotp($secret, 8)); + $this->assertEquals('520489', $this->Otp->hotp($secret, 9)); + } + + /** + * Tests TOTP general construction + * + * Still uses the hotp function, but since totp is a bit more special, has + * its own tests + * Using test vectors from RFC + * https://tools.ietf.org/html/rfc6238 + */ + public function testTotpRfc() + { + $secret = $this->secret; + + // Test vectors are in 8 digits + $this->Otp->setDigits(8); + + // The time presented in the test vector has to be first divided through 30 + // to count as the key + + // SHA 1 grouping + $this->assertEquals('94287082', $this->Otp->hotp($secret, floor(59/30)), 'sha1 with time 59'); + $this->assertEquals('07081804', $this->Otp->hotp($secret, floor(1111111109/30)), 'sha1 with time 1111111109'); + $this->assertEquals('14050471', $this->Otp->hotp($secret, floor(1111111111/30)), 'sha1 with time 1111111111'); + $this->assertEquals('89005924', $this->Otp->hotp($secret, floor(1234567890/30)), 'sha1 with time 1234567890'); + $this->assertEquals('69279037', $this->Otp->hotp($secret, floor(2000000000/30)), 'sha1 with time 2000000000'); + $this->assertEquals('65353130', $this->Otp->hotp($secret, floor(20000000000/30)), 'sha1 with time 20000000000'); + + /* + The following tests do NOT pass. + Once the otp class can deal with these correctly, they can be used again. + They are here for completeness test vectors from the RFC. + + // SHA 256 grouping + $this->Otp->setAlgorithm('sha256'); + $this->assertEquals('46119246', $this->Otp->hotp($secret, floor(59/30)), 'sha256 with time 59'); + $this->assertEquals('07081804', $this->Otp->hotp($secret, floor(1111111109/30)), 'sha256 with time 1111111109'); + $this->assertEquals('14050471', $this->Otp->hotp($secret, floor(1111111111/30)), 'sha256 with time 1111111111'); + $this->assertEquals('89005924', $this->Otp->hotp($secret, floor(1234567890/30)), 'sha256 with time 1234567890'); + $this->assertEquals('69279037', $this->Otp->hotp($secret, floor(2000000000/30)), 'sha256 with time 2000000000'); + $this->assertEquals('65353130', $this->Otp->hotp($secret, floor(20000000000/30)), 'sha256 with time 20000000000'); + + // SHA 512 grouping + $this->Otp->setAlgorithm('sha512'); + $this->assertEquals('90693936', $this->Otp->hotp($secret, floor(59/30)), 'sha512 with time 59'); + $this->assertEquals('25091201', $this->Otp->hotp($secret, floor(1111111109/30)), 'sha512 with time 1111111109'); + $this->assertEquals('99943326', $this->Otp->hotp($secret, floor(1111111111/30)), 'sha512 with time 1111111111'); + $this->assertEquals('93441116', $this->Otp->hotp($secret, floor(1234567890/30)), 'sha512 with time 1234567890'); + $this->assertEquals('38618901', $this->Otp->hotp($secret, floor(2000000000/30)), 'sha512 with time 2000000000'); + $this->assertEquals('47863826', $this->Otp->hotp($secret, floor(20000000000/30)), 'sha512 with time 20000000000'); + */ + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Counter must be integer + */ + public function testHotpInvalidCounter() + { + $this->Otp->hotp($this->secret, 'a'); + } + +} |