From 9e2b2a32fd0e967ad3184e9a5d091a29953acb91 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Wed, 25 Oct 2017 16:22:10 -0700 Subject: Include composer dependencies in repo --- vendor/christian-riesen/base32/.gitignore | 3 + vendor/christian-riesen/base32/.scrutinizer.yml | 14 + vendor/christian-riesen/base32/.travis.yml | 19 ++ vendor/christian-riesen/base32/LICENSE | 19 ++ vendor/christian-riesen/base32/README.md | 60 ++++ vendor/christian-riesen/base32/build.xml | 130 +++++++++ vendor/christian-riesen/base32/composer.json | 33 +++ vendor/christian-riesen/base32/phpunit.xml.dist | 32 +++ vendor/christian-riesen/base32/src/Base32.php | 146 ++++++++++ .../christian-riesen/base32/tests/Base32Test.php | 51 ++++ vendor/christian-riesen/base32/tests/bootstrap.php | 5 + vendor/christian-riesen/otp/.gitignore | 5 + vendor/christian-riesen/otp/.travis.yml | 11 + vendor/christian-riesen/otp/LICENSE | 20 ++ vendor/christian-riesen/otp/README.md | 105 +++++++ vendor/christian-riesen/otp/composer.json | 28 ++ vendor/christian-riesen/otp/example/index.php | 100 +++++++ vendor/christian-riesen/otp/phpunit.xml.dist | 15 + .../otp/src/Otp/GoogleAuthenticator.php | 189 +++++++++++++ vendor/christian-riesen/otp/src/Otp/Otp.php | 310 +++++++++++++++++++++ .../christian-riesen/otp/src/Otp/OtpInterface.php | 65 +++++ .../otp/tests/Otp/GoogleAuthenticatorTest.php | 88 ++++++ vendor/christian-riesen/otp/tests/Otp/OtpTest.php | 124 +++++++++ 23 files changed, 1572 insertions(+) create mode 100644 vendor/christian-riesen/base32/.gitignore create mode 100644 vendor/christian-riesen/base32/.scrutinizer.yml create mode 100644 vendor/christian-riesen/base32/.travis.yml create mode 100644 vendor/christian-riesen/base32/LICENSE create mode 100644 vendor/christian-riesen/base32/README.md create mode 100644 vendor/christian-riesen/base32/build.xml create mode 100644 vendor/christian-riesen/base32/composer.json create mode 100644 vendor/christian-riesen/base32/phpunit.xml.dist create mode 100644 vendor/christian-riesen/base32/src/Base32.php create mode 100644 vendor/christian-riesen/base32/tests/Base32Test.php create mode 100644 vendor/christian-riesen/base32/tests/bootstrap.php create mode 100644 vendor/christian-riesen/otp/.gitignore create mode 100644 vendor/christian-riesen/otp/.travis.yml create mode 100644 vendor/christian-riesen/otp/LICENSE create mode 100644 vendor/christian-riesen/otp/README.md create mode 100644 vendor/christian-riesen/otp/composer.json create mode 100644 vendor/christian-riesen/otp/example/index.php create mode 100644 vendor/christian-riesen/otp/phpunit.xml.dist create mode 100644 vendor/christian-riesen/otp/src/Otp/GoogleAuthenticator.php create mode 100644 vendor/christian-riesen/otp/src/Otp/Otp.php create mode 100644 vendor/christian-riesen/otp/src/Otp/OtpInterface.php create mode 100644 vendor/christian-riesen/otp/tests/Otp/GoogleAuthenticatorTest.php create mode 100644 vendor/christian-riesen/otp/tests/Otp/OtpTest.php (limited to 'vendor/christian-riesen') diff --git a/vendor/christian-riesen/base32/.gitignore b/vendor/christian-riesen/base32/.gitignore new file mode 100644 index 00000000..23f1f99d --- /dev/null +++ b/vendor/christian-riesen/base32/.gitignore @@ -0,0 +1,3 @@ +composer.lock +build/ +vendor/ diff --git a/vendor/christian-riesen/base32/.scrutinizer.yml b/vendor/christian-riesen/base32/.scrutinizer.yml new file mode 100644 index 00000000..cf8b9954 --- /dev/null +++ b/vendor/christian-riesen/base32/.scrutinizer.yml @@ -0,0 +1,14 @@ +before_commands: + - "composer install --prefer-dist" + +tools: + php_mess_detector: true + php_code_sniffer: true + php_analyzer: true + sensiolabs_security_checker: true + php_code_coverage: true + php_cpd: true + php_pdepend: + excluded_dirs: [vendor/, tests/*] +filter: + excluded_paths: [vendor/] diff --git a/vendor/christian-riesen/base32/.travis.yml b/vendor/christian-riesen/base32/.travis.yml new file mode 100644 index 00000000..0b0505e6 --- /dev/null +++ b/vendor/christian-riesen/base32/.travis.yml @@ -0,0 +1,19 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - composer install + +before_script: + - curl -s http://getcomposer.org/installer | php + - php composer.phar update --dev --no-interaction + - mkdir -p build/logs + +script: + - php vendor/bin/phpunit --coverage-text diff --git a/vendor/christian-riesen/base32/LICENSE b/vendor/christian-riesen/base32/LICENSE new file mode 100644 index 00000000..624fceb1 --- /dev/null +++ b/vendor/christian-riesen/base32/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2014 Christian Riesen + +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/base32/README.md b/vendor/christian-riesen/base32/README.md new file mode 100644 index 00000000..00d693d6 --- /dev/null +++ b/vendor/christian-riesen/base32/README.md @@ -0,0 +1,60 @@ +base32 +====== + +Base32 Encoder/Decoder for PHP according to RFC 4648 + +[![Build Status](https://secure.travis-ci.org/ChristianRiesen/base32.png)](http://travis-ci.org/ChristianRiesen/base32) +[![HHVM Status](http://hhvm.h4cc.de/badge/christian-riesen/base32.png)](http://hhvm.h4cc.de/package/christian-riesen/base32) + +[![Latest Stable Version](https://poser.pugx.org/christian-riesen/base32/v/stable.png)](https://packagist.org/packages/christian-riesen/base32) [![Total Downloads](https://poser.pugx.org/christian-riesen/base32/downloads.png)](https://packagist.org/packages/christian-riesen/base32) [![Latest Unstable Version](https://poser.pugx.org/christian-riesen/base32/v/unstable.png)](https://packagist.org/packages/christian-riesen/base32) [![License](https://poser.pugx.org/christian-riesen/base32/license.png)](https://packagist.org/packages/christian-riesen/base32) + +Do you like this? Flattr it: + +[![Flattr base32](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/720563/ChristianRiesenbase32-on-GitHub) + +Usage +----- + + http://christianriesen.com + +Acknowledgements +---------------- + +Base32 is mostly based on the work of https://github.com/NTICompass/PHP-Base32 + diff --git a/vendor/christian-riesen/base32/build.xml b/vendor/christian-riesen/base32/build.xml new file mode 100644 index 00000000..c06a49f9 --- /dev/null +++ b/vendor/christian-riesen/base32/build.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/christian-riesen/base32/composer.json b/vendor/christian-riesen/base32/composer.json new file mode 100644 index 00000000..0c61fbf7 --- /dev/null +++ b/vendor/christian-riesen/base32/composer.json @@ -0,0 +1,33 @@ +{ + "name": "christian-riesen/base32", + "type": "library", + "description": "Base32 encoder/decoder according to RFC 4648", + "keywords": ["base32","encode","decode","rfc4648"], + "homepage": "https://github.com/ChristianRiesen/base32", + "license": "MIT", + "authors": [ + { + "name": "Christian Riesen", + "email": "chris.riesen@gmail.com", + "homepage": "http://christianriesen.com", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "0.*" + }, + "autoload": { + "psr-4": { + "Base32\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/christian-riesen/base32/phpunit.xml.dist b/vendor/christian-riesen/base32/phpunit.xml.dist new file mode 100644 index 00000000..3e2def6f --- /dev/null +++ b/vendor/christian-riesen/base32/phpunit.xml.dist @@ -0,0 +1,32 @@ + + + + + tests/ + + + + + + src/ + + + + + + + + + diff --git a/vendor/christian-riesen/base32/src/Base32.php b/vendor/christian-riesen/base32/src/Base32.php new file mode 100644 index 00000000..bf790182 --- /dev/null +++ b/vendor/christian-riesen/base32/src/Base32.php @@ -0,0 +1,146 @@ + + * @link http://christianriesen.com + * @license MIT License see LICENSE file + */ +class Base32 +{ + /** + * Alphabet for encoding and decoding base32 + * + * @var array + */ + private static $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567='; + + /** + * Creates an array from a binary string into a given chunk size + * + * @param string $binaryString String to chunk + * @param integer $bits Number of bits per chunk + * @return array + */ + private static function chunk($binaryString, $bits) + { + $binaryString = chunk_split($binaryString, $bits, ' '); + + if (substr($binaryString, (strlen($binaryString)) - 1) == ' ') { + $binaryString = substr($binaryString, 0, strlen($binaryString)-1); + } + + return explode(' ', $binaryString); + } + + /** + * Encodes into base32 + * + * @param string $string Clear text string + * @return string Base32 encoded string + */ + public static function encode($string) + { + if (strlen($string) == 0) { + // Gives an empty string + + return ''; + } + + // Convert string to binary + $binaryString = ''; + + foreach (str_split($string) as $s) { + // Return each character as an 8-bit binary string + $binaryString .= sprintf('%08b', ord($s)); + } + + // Break into 5-bit chunks, then break that into an array + $binaryArray = self::chunk($binaryString, 5); + + // Pad array to be divisible by 8 + while (count($binaryArray) % 8 !== 0) { + $binaryArray[] = null; + } + + $base32String = ''; + + // Encode in base32 + foreach ($binaryArray as $bin) { + $char = 32; + + if (!is_null($bin)) { + // Pad the binary strings + $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT); + $char = bindec($bin); + } + + // Base32 character + $base32String .= self::$alphabet[$char]; + } + + return $base32String; + } + + /** + * Decodes base32 + * + * @param string $base32String Base32 encoded string + * @return string Clear text string + */ + public static function decode($base32String) + { + // Only work in upper cases + $base32String = strtoupper($base32String); + + // Remove anything that is not base32 alphabet + $pattern = '/[^A-Z2-7]/'; + + $base32String = preg_replace($pattern, '', $base32String); + + if (strlen($base32String) == 0) { + // Gives an empty string + return ''; + } + + $base32Array = str_split($base32String); + + $string = ''; + + foreach ($base32Array as $str) { + $char = strpos(self::$alphabet, $str); + + // Ignore the padding character + if ($char !== 32) { + $string .= sprintf('%05b', $char); + } + } + + while (strlen($string) %8 !== 0) { + $string = substr($string, 0, strlen($string)-1); + } + + $binaryArray = self::chunk($string, 8); + + $realString = ''; + + foreach ($binaryArray as $bin) { + // Pad each value to 8 bits + $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT); + // Convert binary strings to ASCII + $realString .= chr(bindec($bin)); + } + + return $realString; + } +} diff --git a/vendor/christian-riesen/base32/tests/Base32Test.php b/vendor/christian-riesen/base32/tests/Base32Test.php new file mode 100644 index 00000000..3e5924ce --- /dev/null +++ b/vendor/christian-riesen/base32/tests/Base32Test.php @@ -0,0 +1,51 @@ +decode() + * + * Testing test vectors according to RFC 4648 + * http://www.ietf.org/rfc/rfc4648.txt + */ + public function testDecode() + { + // RFC test vectors say that empty string returns empty string + $this->assertEquals('', Base32::decode('')); + + // these strings are taken from the RFC + $this->assertEquals('f', Base32::decode('MY======')); + $this->assertEquals('fo', Base32::decode('MZXQ====')); + $this->assertEquals('foo', Base32::decode('MZXW6===')); + $this->assertEquals('foob', Base32::decode('MZXW6YQ=')); + $this->assertEquals('fooba', Base32::decode('MZXW6YTB')); + $this->assertEquals('foobar', Base32::decode('MZXW6YTBOI======')); + + // Decoding a string made up entirely of invalid characters + $this->assertEquals('', Base32::decode('8908908908908908')); + } + + /** + * Encoder tests, reverse of the decodes + */ + public function testEncode() + { + // RFC test vectors say that empty string returns empty string + $this->assertEquals('', Base32::encode('')); + + // these strings are taken from the RFC + $this->assertEquals('MY======', Base32::encode('f')); + $this->assertEquals('MZXQ====', Base32::encode('fo')); + $this->assertEquals('MZXW6===', Base32::encode('foo')); + $this->assertEquals('MZXW6YQ=', Base32::encode('foob')); + $this->assertEquals('MZXW6YTB', Base32::encode('fooba')); + $this->assertEquals('MZXW6YTBOI======', Base32::encode('foobar')); + } +} diff --git a/vendor/christian-riesen/base32/tests/bootstrap.php b/vendor/christian-riesen/base32/tests/bootstrap.php new file mode 100644 index 00000000..12bea5b1 --- /dev/null +++ b/vendor/christian-riesen/base32/tests/bootstrap.php @@ -0,0 +1,5 @@ +add("Base32", __DIR__); +$loader->register(); 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 +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 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 @@ +totp(Base32::decode($secret)); + +$qrCode = GoogleAuthenticator::getQrCodeUrl('totp', 'otpsample@cr', $secret); +$keyUri = GoogleAuthenticator::getKeyUri('totp', 'otpsample@cr', $secret); + +?> + +One Time Passwords Example + + + +

One Time Passwords Example

+ +Secret is . This is saved with the users credentials. +
+
+
+ +QR Code for totp:
+ +
+This QR Code contains the Key URI: +
+
+ +Current totp would be
+
+
+ +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. +
+
+ +
+ +
+Output:
+
+ + +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'; + } +} + +?> + + + 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 @@ + + + + + + tests/Otp/ + + + + + + src/Otp/ + + + 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 @@ + + * @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 @@ + + * @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 @@ + + * @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 @@ +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 @@ +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'); + } + +} -- cgit v1.2.3