Compare commits

...

47 Commits

Author SHA1 Message Date
RobThree fca87f2d09 * Fix a bug that caused timeslices to be returned instead of the intended timeslice
* Upped version
2018-06-06 02:32:07 +02:00
Rob Janssen 72572c5c3a * Removed hhvm badge 2018-06-06 01:59:46 +02:00
RobThree a813bf7ede * Upped version to 1.6.3 2018-06-06 01:58:26 +02:00
Rob Janssen 39db2654a6 Update README.md 2018-06-06 01:56:01 +02:00
RobThree 4ac2670429 * Try older PHP versions for the last time 2018-06-06 01:50:29 +02:00
RobThree 5de91c2837 * Ignore deprecation message for mcrypt_create_iv for PHP7.1+ 2018-06-06 01:49:21 +02:00
RobThree 0ac68f6b86 * Fix PHP7.2 issue with HashRNGProvider
* Fix PHP7.1+ issue with MCryptRNGProvider
2018-06-06 01:47:03 +02:00
RobThree fac4ebd44b * Remove time.google.com to see if that will make travis build again 2018-06-06 01:38:51 +02:00
RobThree ad8b7ab3e8 * Trying some build configurations 2018-06-06 01:35:56 +02:00
RobThree d7e8ad8e23 * Trying some build configurations 2018-06-06 01:32:25 +02:00
RobThree b8befc5aff * Put back conditional phpunit running 2018-06-06 01:15:37 +02:00
RobThree 37fbb99ac5 * Run phpunit for all versions 2018-06-06 00:58:30 +02:00
RobThree ea9b87fe32 * Changed time.windows.com to time.google.com (doesn't affect anything except that it should stop travis from failing) 2018-06-06 00:53:02 +02:00
RobThree 4f2364fef4 * Add whitelist to phpunit.xml 2018-06-05 23:05:08 +02:00
RobThree e4ec94e14d * Drop PHP 5.3 again
* Add phpunit.xml
2018-06-05 23:02:37 +02:00
RobThree de210192a7 * Put back PHP 5.3 support 2018-06-05 22:46:16 +02:00
RobThree f25b910be9 * Put back phpunit in travis.yml 2018-06-05 22:42:00 +02:00
RobThree b591c879a1 * Put back dist: trusty in travis.yml 2018-06-05 22:39:47 +02:00
RobThree 4408ce7884 * Dropped support for PHP 5.3
* Added support for PHP 7.2
2018-06-05 22:36:40 +02:00
RobThree 83ad9fb4e4 * Updated composer.json 2018-06-05 22:20:08 +02:00
RobThree 93fc6355d4 * Dropped ConvertUnixTimeDotComTimeProvider
* Added NTPTimeProvider
* Added out argument on verifyCode() method that returns the matched timeslice (if any) when a valid code was entered
2018-06-05 22:17:49 +02:00
Rob Janssen a77e7d8223 Update README.md 2017-11-06 18:55:56 +01:00
RobThree 673223a2fc * Upped version 2017-11-06 18:54:34 +01:00
RobThree d450ba432d * Attempt 2 to fix Travis 2017-11-06 18:50:34 +01:00
RobThree d22329375f * Attempt to fix Travis build 2017-11-06 18:48:32 +01:00
RobThree b1ecc9afbc * Fix some caching issues 2017-11-06 18:37:49 +01:00
Rob Janssen c9bfe0519a Undo previous commit 2017-02-18 15:43:20 +01:00
Rob Janssen 404f147aa7 Add PHP 7.1 and nightly 2017-02-18 15:37:04 +01:00
Rob Janssen 18888f83ee Delete applicationhost.config 2017-02-17 18:00:58 +01:00
RobThree 3d04717346 * Add .vs folder to .gitignore 2017-02-17 18:00:19 +01:00
RobThree 5093ab230c * Fix TwoFactorAuth class actually uses the TimeProvider
* Update README and DEMO to advise using 160+ bits secret
2017-02-17 16:24:54 +01:00
RobThree f90bc37319 * You can now specify the expected time format returned by a 3rd party to the HttpTimeProvider. 2017-02-17 15:21:31 +01:00
RobThree 0ebe1d69a6 * Less opinionated SSL defaults 2017-02-17 15:14:58 +01:00
RobThree 78a2d6e514 * Default stream options can now be overridden by specifying them in the constructor
* Removed some deprecated/stale code
2017-02-17 15:12:34 +01:00
RobThree 582a26749a * Fix another PHP5.3 array issue 2017-02-17 15:02:02 +01:00
RobThree c114772d01 * Fix PHP 5.3 array syntax in unittests 2017-02-17 14:59:44 +01:00
RobThree 9735116635 * Implemented ITimeProviders 2017-02-17 14:53:55 +01:00
RobThree 178c60d947 * Redo #15 which somehow didn't merge... 2017-02-17 12:15:17 +01:00
Rob Janssen b3bb9685ea Merge pull request #14 from martijncalker/fix-example-readme
Fix php examples in readme
2017-01-03 10:42:48 +01:00
Martijn van Calker d03e976891 Fix php example in readme 2017-01-03 09:48:55 +01:00
Rob Janssen 0b5d455b27 Update composer.json 2016-10-30 22:47:00 +01:00
Rob Janssen aac54360a9 Merge pull request #10 from anvyst/master
switching to PSR-4 autoloading standard
2016-10-30 22:40:45 +01:00
Andrey Vystavkin a73f119a58 removed .editconfig from repo and phpcs 2016-10-30 16:38:10 +02:00
Andrey Vystavkin 8a726eda5d switching to PSR-4 autoloading standard 2016-10-30 16:20:47 +02:00
Rob Janssen fa781c14e3 Update composer.json 2016-06-24 17:52:02 +02:00
Rob Janssen ab6b059df5 Merge pull request #7 from JonathanTru/master
changed getQRText() from private to public
2016-06-24 17:50:15 +02:00
Jonathan Trummer aec91881bf changed getQRText() from private to public
Changed access modifier for the function getQRText() from private to
public.
This allows for generating the QR Code in different ways (eg pass the
data from getQRText() to the client and have a Javascript Library render
the QR Code)
2016-06-24 17:34:39 +02:00
25 changed files with 1457 additions and 173 deletions
+6
View File
@@ -181,3 +181,9 @@ UpgradeLog*.htm
# Microsoft Fakes
FakesAssemblies/
# Composer
/vendor
# .vs
.vs/
+8 -4
View File
@@ -1,11 +1,15 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7
- hhvm
- 7.0
- 7.1
- 7.2
script: phpunit --coverage-text tests
before_script:
- composer install
script:
- vendor/bin/phpunit --coverage-text tests
+28 -12
View File
@@ -1,6 +1,6 @@
# ![Logo](https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/logo.png) PHP library for Two Factor Authentication
[![Build status](https://img.shields.io/travis/RobThree/TwoFactorAuth.svg?style=flat-square)](https://travis-ci.org/RobThree/TwoFactorAuth/) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![HHVM Status](https://img.shields.io/hhvm/RobThree/TwoFactorAuth.svg?style=flat-square)](http://hhvm.h4cc.de/package/robthree/twofactorauth) [![Code Climate](https://img.shields.io/codeclimate/github/RobThree/TwoFactorAuth.svg?style=flat-square)](https://codeclimate.com/github/RobThree/TwoFactorAuth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
[![Build status](https://img.shields.io/travis/RobThree/TwoFactorAuth.svg?style=flat-square)](https://travis-ci.org/RobThree/TwoFactorAuth/) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![Code Climate](https://img.shields.io/codeclimate/github/RobThree/TwoFactorAuth.svg?style=flat-square)](https://codeclimate.com/github/RobThree/TwoFactorAuth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) using [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) and [QR-codes](http://en.wikipedia.org/wiki/QR_code). Inspired by, based on but most importantly an *improvement* on '[PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator)'. There's a [.Net implementation](https://github.com/RobThree/TwoFactorAuth.Net) of this library as well.
@@ -10,7 +10,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
## Requirements
* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7 and HHVM
* Tested on PHP 5.4 up to 7.2
* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
* [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
@@ -33,7 +33,7 @@ Here are some code snippets that should help you get started...
$tfa = new RobThree\Auth\TwoFactorAuth('My Company');
````
The TwoFactorAuth class constructor accepts 6 parameters (all optional):
The TwoFactorAuth class constructor accepts 7 parameters (all optional):
Parameter | Default value | Use
------------------|---------------|--------------------------------------------------
@@ -43,6 +43,7 @@ Parameter | Default value | Use
`$algorithm` | `sha1` | The algorithm used
`$qrcodeprovider` | `null` | QR-code provider (more on this later)
`$rngprovider` | `null` | Random Number Generator provider (more on this later)
`$timeprovider` | `null` | Time provider (more on this later)
These parameters are all '`write once`'; the class will, for it's lifetime, use these values when generating / calculating codes. The number of digits, the period and algorithm are all set to values Google's Authticator app uses (and supports). You may specify `8` digits, a period of `45` seconds and the `sha256` algorithm but the authenticator app (be it Google's implementation, Authy or any other app) may or may not support these values. Your mileage may vary; keep it on the safe side if you don't control which app your audience uses.
@@ -54,11 +55,11 @@ When a user wants to setup two-factor auth (or, more correctly, multi-factor aut
$secret = $tfa->createSecret();
````
The `createSecret()` method accepts two arguments: `$bits` (default: `80`) and `$requirecryptosecure` (default: `true`). The former is the number of bits generated for the shared secret. Make sure this argument is a multiple of 8 and, again, keep in mind that not all combinations may be supported by all apps. Google authenticator seems happy with 80 and 160, the default is set to 80 because that's what most sites (that I know of) currently use. The latter is used to ensure that the secret is cryptographically secure; if you don't care very much for cryptographically secure secrets you can specify `false` and use a **non**-cryptographically secure RNG provider.
The `createSecret()` method accepts two arguments: `$bits` (default: `80`) and `$requirecryptosecure` (default: `true`). The former is the number of bits generated for the shared secret. Make sure this argument is a multiple of 8 and, again, keep in mind that not all combinations may be supported by all apps. Google authenticator seems happy with 80 and 160, the default is set to 80 because that's what most sites (that I know of) currently use; however a value of 160 or higher is recommended (see [RFC 4226 - Algorithm Requirements](https://tools.ietf.org/html/rfc4226#section-4)). The latter is used to ensure that the secret is cryptographically secure; if you don't care very much for cryptographically secure secrets you can specify `false` and use a **non**-cryptographically secure RNG provider.
````php
// Display shared secret
<p>Please enter the following code in your app: '<?php echo $secret ?>'</p>
<p>Please enter the following code in your app: '<?php echo $secret; ?>'</p>
````
Another, more user-friendly, way to get the shared secret into the app is to generate a [QR-code](http://en.wikipedia.org/wiki/QR_code) which can be scanned by the app. To generate these QR codes you can use any one of the built-in `QRProvider` classes:
@@ -74,7 +75,7 @@ The built-in providers all have some provider-specific 'tweaks' you can 'apply'.
````php
// Display QR code to user
<p>Scan the following image with your app:</p>
<p><img src="<?php $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret) ?>"></p>
<p><img src="<?php echo $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret); ?>"></p>
````
When outputting a QR-code you can choose a `$label` for the user (which, when entering a shared secret manually, will have to be chosen by the user). This label may be an empty string or `null`. Also a `$size` may be specified (in pixels, width == height) for which we use a default value of `200`.
@@ -101,10 +102,17 @@ Simple as 1-2-3.
All we need is 3 methods and a constructor:
````php
__construct($issuer=null, $digits=6, $period=30, $algorithm='sha1', $qrcodeprovider=null, $rngprovider=null)
createSecret($bits = 80, $requirecryptosecure = true)
getQRCodeImageAsDataUri($label, $secret, $size = 200)
verifyCode($secret, $code, $discrepancy = 1, $time = null)
public function __construct(
$issuer = null,
$digits = 6,
$period = 30,
$algorithm = 'sha1',
RobThree\Auth\Providers\Qr\IQRCodeProvider $qrcodeprovider = null,
RobThree\Auth\Providers\Rng\IRNGProvider $rngprovider = null
);
public function createSecret($bits = 80, $requirecryptosecure = true): string;
public function getQRCodeImageAsDataUri($label, $secret, $size = 200): string;
public function verifyCode($secret, $code, $discrepancy = 1, $time = null): bool;
````
### QR-code providers
@@ -132,7 +140,7 @@ Let's see if we can use [PHP QR Code](http://phpqrcode.sourceforge.net/) to impl
<?php
require_once '../../phpqrcode.php'; // Yeah, we're gonna need that
namespace RobThree\Auth\Providers\Qr
namespace RobThree\Auth\Providers\Qr;
class MyProvider implements IQRCodeProvider {
public function getMimeType() {
@@ -159,7 +167,7 @@ $mp = new RobThree\Auth\Providers\Qr\MyProvider();
$tfa = new RobThree\Auth\TwoFactorAuth('My Company', 6, 30, 'sha1', $mp);
$secret = $tfa->createSecret();
?>
<p><img src="<?php $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret) ?>"></p>
<p><img src="<?php echo $tfa->getQRCodeImageAsDataUri('Bob Ross', $secret); ?>"></p>
````
Voilà. Couldn't make it any simpler.
@@ -170,6 +178,14 @@ This library also comes with three 'built-in' RNG providers ([Random Number Gene
You can easily implement your own `RNGProvider` by simply implementing the `IRNGProvider` interface. Each of the 'built-in' RNG providers have some constructor parameters that allow you to 'tweak' some of the settings to use when creating the random bytes such as which source to use (`MCryptRNGProvider`) or which hashing algorithm (`HashRNGProvider`). I encourage you to have a look at some of the ['built-in' RNG providers](lib/Providers/Rng) for details and the [`IRNGProvider` interface](lib/Providers/Rng/IRNGProvider.php).
### Time providers
Another set of providers in this library are the Time Providers; this library provides three 'built-in' ones. The default Time Provider used is the [`LocalMachineTimeProvider`](lib/Providers/Time/LocalMachineTimeProvider.php); this provider simply returns the output of `Time()` and is *highly recommended* as default provider. The [`HttpTimeProvider`](lib/Providers/Time/HttpTimeProvider.php) executes a `HEAD` request against a given webserver (default: google.com) and tries to extract the `Date:`-HTTP header and returns it's date. Other url's/domains can be used by specifying the url in the constructor. The final Time Provider is the [`NTPTimeProvider`](lib/Providers/Time/NTPTimeProvider.php) which does an NTP request to a specified NTP server.
You can easily implement your own `TimeProvider` by simply implementing the `ITimeProvider` interface.
As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to Google's and convert-unix-time.com's current time. You can pass an array of `ITimeProvider`s and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `NTPTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly.
## Integrations
- [CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)
+10
View File
@@ -14,6 +14,8 @@
<PHPDevPort>41315</PHPDevPort>
<PHPDevHostName>localhost</PHPDevHostName>
<IISProjectUrl>http://localhost:41315/</IISProjectUrl>
<Runtime>PHP</Runtime>
<RuntimeVersion>7.0</RuntimeVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<IncludeDebugInformation>true</IncludeDebugInformation>
@@ -36,6 +38,11 @@
<Compile Include="lib\Providers\Rng\OpenSSLRNGProvider.php" />
<Compile Include="lib\Providers\Rng\HashRNGProvider.php" />
<Compile Include="lib\Providers\Rng\RNGException.php" />
<Compile Include="lib\Providers\Time\HttpTimeProvider.php" />
<Compile Include="lib\Providers\Time\ITimeProvider.php" />
<Compile Include="lib\Providers\Time\LocalMachineTimeProvider.php" />
<Compile Include="lib\Providers\Time\NTPTimeProvider.php" />
<Compile Include="lib\Providers\Time\TimeException.php" />
<Compile Include="lib\TwoFactorAuth.php" />
<Compile Include=".gitignore" />
<Compile Include="README.md" />
@@ -45,6 +52,7 @@
<ItemGroup>
<Folder Include="lib\" />
<Folder Include="lib\Providers\" />
<Folder Include="lib\Providers\Time\" />
<Folder Include="lib\Providers\Qr\" />
<Folder Include="lib\Providers\Rng\" />
<Folder Include="demo\" />
@@ -53,8 +61,10 @@
<ItemGroup>
<Content Include=".travis.yml" />
<Content Include="composer.json" />
<Content Include="composer.lock" />
<Content Include="logo.png" />
<Content Include="multifactorauthforeveryone.png" />
<Content Include="LICENSE" />
<Content Include="phpunit.xml" />
</ItemGroup>
</Project>
+14 -6
View File
@@ -1,7 +1,7 @@
{
"name": "robthree/twofactorauth",
"description": "Two Factor Authentication",
"version": "1.5",
"version": "1.6.4",
"type": "library",
"keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
"homepage": "https://github.com/RobThree/TwoFactorAuth",
@@ -17,12 +17,20 @@
"issues": "https://github.com/RobThree/TwoFactorAuth/issues",
"source": "https://github.com/RobThree/TwoFactorAuth"
},
"autoload": {
"classmap": [
"lib/"
]
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "@stable"
},
"autoload": {
"psr-4": {
"RobThree\\Auth\\": "lib"
}
},
"autoload-dev": {
"psr-4": {
"RobThree\\Auth\\Test\\": "tests"
}
}
}
Generated
+980
View File
@@ -0,0 +1,980 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "9647de85f54ba6db237f5ff42ff85a1f",
"packages": [],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14T21:17:01+00:00"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"phpDocumentor": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"time": "2015-02-03T12:10:50+00:00"
},
{
"name": "phpspec/prophecy",
"version": "v1.6.2",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb",
"reference": "6c52c2722f8460122f96f86346600e1077ce22cb",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
"sebastian/comparator": "^1.1",
"sebastian/recursion-context": "^1.0|^2.0"
},
"require-dev": {
"phpspec/phpspec": "^2.0",
"phpunit/phpunit": "^4.8 || ^5.6.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"time": "2016-11-21T14:58:47+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "~1.3",
"sebastian/environment": "^1.3.2",
"sebastian/version": "~1.0"
},
"require-dev": {
"ext-xdebug": ">=2.1.4",
"phpunit/phpunit": "~4"
},
"suggest": {
"ext-dom": "*",
"ext-xdebug": ">=2.2.1",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2015-10-06T15:47:00+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "1.4.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2016-10-03T07:40:28+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21T13:50:34+00:00"
},
{
"name": "phpunit/php-timer",
"version": "1.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4|~5"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"time": "2016-05-12T18:03:57+00:00"
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.9",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3b402f65a4cc90abf6e1104e388b896ce209631b",
"reference": "3b402f65a4cc90abf6e1104e388b896ce209631b",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"time": "2016-11-15T14:06:22+00:00"
},
{
"name": "phpunit/phpunit",
"version": "4.8.35",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
"reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.2.2",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
"symfony/yaml": "~2.1|~3.0"
},
"suggest": {
"phpunit/php-invoker": "~1.1"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.8.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2017-02-06T05:18:07+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"time": "2015-10-02T06:51:40+00:00"
},
{
"name": "sebastian/comparator",
"version": "1.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
"reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2 || ~2.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2017-01-29T09:50:25+00:00"
},
{
"name": "sebastian/diff",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2015-12-08T07:14:41+00:00"
},
{
"name": "sebastian/environment",
"version": "1.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
"reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"time": "2016-08-18T05:49:44+00:00"
},
{
"name": "sebastian/exporter",
"version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
"reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"ext-mbstring": "*",
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2016-06-17T09:04:28+00:00"
},
{
"name": "sebastian/global-state",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2015-10-12T03:26:01+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-11-11T19:50:13+00:00"
},
{
"name": "sebastian/version",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2015-06-21T13:59:46+00:00"
},
{
"name": "symfony/yaml",
"version": "v2.8.17",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "322a8c2dfbca15ad6b1b27e182899f98ec0e0153"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/322a8c2dfbca15ad6b1b27e182899f98ec0e0153",
"reference": "322a8c2dfbca15ad6b1b27e182899f98ec0e0153",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2017-01-21T16:40:50+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"phpunit/phpunit": 0
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.3.0"
},
"platform-dev": []
}
+10 -2
View File
@@ -8,13 +8,13 @@
<?php
require_once 'loader.php';
Loader::register('../lib','RobThree\\Auth');
use \RobThree\Auth\TwoFactorAuth;
$tfa = new TwoFactorAuth('MyApp');
echo '<li>First create a secret and associate it with a user';
$secret = $tfa->createSecret();
$secret = $tfa->createSecret(160); // Though the default is an 80 bits secret (for backwards compatibility reasons) we recommend creating 160+ bits secrets (see RFC 4226 - Algorithm Requirements)
echo '<li>Next create a QR code and let the user scan it:<br><img src="' . $tfa->getQRCodeImageAsDataUri('My label', $secret) . '"><br>...or display the secret to the user for manual entry: ' . chunk_split($secret, 4, ' ');
$code = $tfa->getCode($secret);
echo '<li>Next, have the user verify the code; at this time the code displayed by a 2FA-app would be: <span style="color:#00c">' . $code . '</span> (but that changes periodically)';
@@ -23,5 +23,13 @@
?>
</ol>
<p>Note: Make sure your server-time is <a href="http://en.wikipedia.org/wiki/Network_Time_Protocol">NTP-synced</a>! Depending on the $discrepancy allowed your time cannot drift too much from the users' time!</p>
<?php
try {
$tfa->ensureCorrectTime();
echo 'Your hosts time seems to be correct / within margin';
} catch (RobThree\Auth\TwoFactorAuthException $ex) {
echo '<b>Warning:</b> Your hosts time seems to be off: ' . $ex->getMessage();
}
?>
</body>
</html>
+1 -1
View File
@@ -11,7 +11,7 @@ class GoogleQRCodeProvider extends BaseHTTPQRCodeProvider
function __construct($verifyssl = false, $errorcorrectionlevel = 'L', $margin = 1)
{
if (!is_bool($verifyssl))
throw new QRException('VerifySSL must be bool');
throw new \QRException('VerifySSL must be bool');
$this->verifyssl = $verifyssl;
+2 -8
View File
@@ -1,11 +1,5 @@
<?php
namespace RobThree\Auth\Providers\Qr;
use RobThree\Auth\TwoFactorAuthException;
class QRException extends \Exception
{
function __construct($message = "", $code = 0, $exception = null)
{
parent::__construct($message, $code, $exception);
}
}
class QRException extends TwoFactorAuthException {}
+1 -1
View File
@@ -43,7 +43,7 @@ class QRServerProvider extends BaseHTTPQRCodeProvider
case 'eps':
return 'application/postscript';
}
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
throw new \QRException(sprintf('Unknown MIME-type: %s', $this->format));
}
public function getQRCodeImage($qrtext, $size)
+1 -1
View File
@@ -33,7 +33,7 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
case 'j':
return 'image/jpeg';
}
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
throw new \QRException(sprintf('Unknown MIME-type: %s', $this->format));
}
public function getQRCodeImage($qrtext, $size)
+2 -2
View File
@@ -8,7 +8,7 @@ class HashRNGProvider implements IRNGProvider
function __construct($algorithm = 'sha256' ) {
$algos = array_values(hash_algos());
if (!in_array($algorithm, $algos, true))
throw new RNGException('Unsupported algorithm specified');
throw new \RNGException('Unsupported algorithm specified');
$this->algorithm = $algorithm;
}
@@ -17,7 +17,7 @@ class HashRNGProvider implements IRNGProvider
$hash = mt_rand();
for ($i = 0; $i < $bytecount; $i++) {
$hash = hash($this->algorithm, $hash.mt_rand(), true);
$result .= $hash[mt_rand(0, sizeof($hash))];
$result .= $hash[mt_rand(0, strlen($hash)-1)];
}
return $result;
}
+2 -2
View File
@@ -11,9 +11,9 @@ class MCryptRNGProvider implements IRNGProvider
}
public function getRandomBytes($bytecount) {
$result = mcrypt_create_iv($bytecount, $this->source);
$result = @mcrypt_create_iv($bytecount, $this->source);
if ($result === false)
throw new RNGException('mcrypt_create_iv returned an invalid value');
throw new \RNGException('mcrypt_create_iv returned an invalid value');
return $result;
}
+2 -2
View File
@@ -13,9 +13,9 @@ class OpenSSLRNGProvider implements IRNGProvider
public function getRandomBytes($bytecount) {
$result = openssl_random_pseudo_bytes($bytecount, $crypto_strong);
if ($this->requirestrong && ($crypto_strong === false))
throw new RNGException('openssl_random_pseudo_bytes returned non-cryptographically strong value');
throw new \RNGException('openssl_random_pseudo_bytes returned non-cryptographically strong value');
if ($result === false)
throw new RNGException('openssl_random_pseudo_bytes returned an invalid value');
throw new \RNGException('openssl_random_pseudo_bytes returned an invalid value');
return $result;
}
+2 -8
View File
@@ -1,11 +1,5 @@
<?php
namespace RobThree\Auth\Providers\Rng;
use RobThree\Auth\TwoFactorAuthException;
class RNGException extends \Exception
{
function __construct($message = "", $code = 0, $exception = null)
{
parent::__construct($message, $code, $exception);
}
}
class RNGException extends TwoFactorAuthException {}
+54
View File
@@ -0,0 +1,54 @@
<?php
namespace RobThree\Auth\Providers\Time;
/**
* Takes the time from any webserver by doing a HEAD request on the specified URL and extracting the 'Date:' header
*/
class HttpTimeProvider implements ITimeProvider
{
public $url;
public $options;
public $expectedtimeformat;
function __construct($url = 'https://google.com', $expectedtimeformat = 'D, d M Y H:i:s O+', array $options = null)
{
$this->url = $url;
$this->expectedtimeformat = $expectedtimeformat;
$this->options = $options;
if ($this->options === null) {
$this->options = array(
'http' => array(
'method' => 'HEAD',
'follow_location' => false,
'ignore_errors' => true,
'max_redirects' => 0,
'request_fulluri' => true,
'header' => array(
'Connection: close',
'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)',
'Cache-Control: no-cache'
)
)
);
}
}
public function getTime() {
try {
$context = stream_context_create($this->options);
$fd = fopen($this->url, 'rb', false, $context);
$headers = stream_get_meta_data($fd);
fclose($fd);
foreach ($headers['wrapper_data'] as $h) {
if (strcasecmp(substr($h, 0, 5), 'Date:') === 0)
return \DateTime::createFromFormat($this->expectedtimeformat, trim(substr($h,5)))->getTimestamp();
}
throw new \TimeException(sprintf('Unable to retrieve time from %s (Invalid or no "Date:" header found)', $this->url));
}
catch (Exception $ex) {
throw new \TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->url, $ex->getMessage()));
}
}
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace RobThree\Auth\Providers\Time;
interface ITimeProvider
{
public function getTime();
}
@@ -0,0 +1,9 @@
<?php
namespace RobThree\Auth\Providers\Time;
class LocalMachineTimeProvider implements ITimeProvider {
public function getTime() {
return time();
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace RobThree\Auth\Providers\Time;
/**
* Takes the time from any NTP server
*/
class NTPTimeProvider implements ITimeProvider
{
public $host;
public $port;
public $timeout;
function __construct($host = 'pool.ntp.org', $port = 123, $timeout = 1)
{
$this->host = $host;
if (!is_int($port) || $port <= 0 || $port > 65535)
throw new \TimeException('Port must be 0 < port < 65535');
$this->port = $port;
if (!is_int($timeout) || $timeout < 0)
throw new \TimeException('Timeout must be >= 0');
$this->timeout = $timeout;
}
public function getTime() {
try {
/* Create a socket and connect to NTP server */
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_connect($sock, $this->host, $this->port);
/* Send request */
$msg = "\010" . str_repeat("\0", 47);
socket_send($sock, $msg, strlen($msg), 0);
/* Receive response and close socket */
socket_recv($sock, $recv, 48, MSG_WAITALL);
socket_close($sock);
/* Interpret response */
$data = unpack('N12', $recv);
$timestamp = sprintf('%u', $data[9]);
/* NTP is number of seconds since 0000 UT on 1 January 1900 Unix time is seconds since 0000 UT on 1 January 1970 */
return $timestamp - 2208988800;
}
catch (Exception $ex) {
throw new \TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->host, $ex->getMessage()));
}
}
}
+5
View File
@@ -0,0 +1,5 @@
<?php
use RobThree\Auth\TwoFactorAuthException;
class TimeException extends TwoFactorAuthException {}
+138 -81
View File
@@ -1,165 +1,173 @@
<?php
namespace RobThree\Auth;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\Providers\Rng\IRNGProvider;
use RobThree\Auth\Providers\Time\ITimeProvider;
// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
class TwoFactorAuth
class TwoFactorAuth
{
private $algorithm;
private $period;
private $digits;
private $issuer;
private $qrcodeprovider;
private $rngprovider;
private $qrcodeprovider = null;
private $rngprovider = null;
private $timeprovider = null;
private static $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
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, $rngprovider = null)
function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1', IQRCodeProvider $qrcodeprovider = null, IRNGProvider $rngprovider = null, ITimeProvider $timeprovider = null)
{
$this->issuer = $issuer;
if (!is_int($digits) || $digits <= 0)
throw new TwoFactorAuthException('Digits must be int > 0');
$this->digits = $digits;
if (!is_int($period) || $period <= 0)
throw new TwoFactorAuthException('Period must be int > 0');
$this->period = $period;
$algorithm = strtolower(trim($algorithm));
if (!in_array($algorithm, self::$_supportedalgos))
throw new TwoFactorAuthException('Unsupported algorithm: ' . $algorithm);
$this->algorithm = $algorithm;
// Set default QR Code provider if none was specified
if ($qrcodeprovider==null)
$qrcodeprovider = new Providers\Qr\GoogleQRCodeProvider();
if (!($qrcodeprovider instanceof Providers\Qr\IQRCodeProvider))
throw new TwoFactorAuthException('QRCodeProvider must implement IQRCodeProvider');
$this->qrcodeprovider = $qrcodeprovider;
// Try to find best available RNG provider if none was specified
if ($rngprovider==null) {
if (function_exists('random_bytes')) {
$rngprovider = new Providers\Rng\CSRNGProvider();
} elseif (function_exists('mcrypt_create_iv')) {
$rngprovider = new Providers\Rng\MCryptRNGProvider();
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$rngprovider = new Providers\Rng\OpenSSLRNGProvider();
} elseif (function_exists('hash')) {
$rngprovider = new Providers\Rng\HashRNGProvider();
} else {
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
}
}
if (!($rngprovider instanceof Providers\Rng\IRNGProvider))
throw new TwoFactorAuthException('RNGProvider must implement IRNGProvider');
$this->rngprovider = $rngprovider;
$this->timeprovider = $timeprovider;
self::$_base32 = str_split(self::$_base32dict);
self::$_base32lookup = array_flip(self::$_base32);
}
/**
* Create a new secret
*/
public function createSecret($bits = 80, $requirecryptosecure = true)
public function createSecret($bits = 80, $requirecryptosecure = true)
{
$secret = '';
$bytes = ceil($bits / 5); //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
if ($requirecryptosecure && !$this->rngprovider->isCryptographicallySecure())
$rngprovider = $this->getRngprovider();
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure())
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
$rnd = $this->rngprovider->getRandomBytes($bytes);
$rnd = $rngprovider->getRandomBytes($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);
$timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
$hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true); // Hash it with users secret key
$hashpart = substr($hashhmac, ord(substr($hashhmac, -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)
public function verifyCode($secret, $code, $discrepancy = 1, $time = null, &$timeslice = 0)
{
$result = false;
$timetamp = $this->getTime($time);
// To keep safe from timing-attachs we iterate *all* possible codes even though we already may have verified a code is correct
for ($i = -$discrepancy; $i <= $discrepancy; $i++)
$result |= $this->codeEquals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
return (bool)$result;
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
$ts = $timetamp + ($i * $this->period);
$slice = $this->getTimeSlice($ts);
$timeslice += $this->codeEquals($this->getCode($secret, $ts), $code) ? $slice : 0;
}
return $timeslice > 0;
}
/**
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
*/
private function codeEquals($safe, $user) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $user);
} else {
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
// we don't leak information about the difference of the two strings.
if (strlen($safe)===strlen($user)) {
$result = 0;
for ($i = 0; $i < strlen($safe); $i++)
$result |= (ord($safe[$i]) ^ ord($user[$i]));
return $result === 0;
}
}
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
// we don't leak information about the difference of the two strings.
if (strlen($safe)===strlen($user)) {
$result = 0;
for ($i = 0; $i < strlen($safe); $i++)
$result |= (ord($safe[$i]) ^ ord($user[$i]));
return $result === 0;
}
return false;
}
/**
* Get data-uri of QRCode
*/
public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
{
if (!is_int($size) || $size <= 0)
throw new TwoFactorAuthException('Size must be int > 0');
$qrcodeprovider = $this->getQrCodeProvider();
return 'data:'
. $this->qrcodeprovider->getMimeType()
. $qrcodeprovider->getMimeType()
. ';base64,'
. base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
. base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
}
private function getTime($time)
/**
* Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
*/
public function ensureCorrectTime(array $timeproviders = null, $leniency = 5)
{
return ($time === null) ? time() : $time;
if ($timeproviders != null && !is_array($timeproviders))
throw new TwoFactorAuthException('No timeproviders specified');
if ($timeproviders == null)
$timeproviders = array(
new Providers\Time\NTPTimeProvider(),
new Providers\Time\HttpTimeProvider()
);
// Get default time provider
$timeprovider = $this->getTimeProvider();
// Iterate specified time providers
foreach ($timeproviders as $t) {
if (!($t instanceof ITimeProvider))
throw new TwoFactorAuthException('Object does not implement ITimeProvider');
// Get time from default time provider and compare to specific time provider and throw if time difference is more than specified number of seconds leniency
if (abs($timeprovider->getTime() - $t->getTime()) > $leniency)
throw new TwoFactorAuthException(sprintf('Time for timeprovider is off by more than %d seconds when compared to %s', $leniency, get_class($t)));
}
}
private function getTimeSlice($time = null, $offset = 0)
private function getTime($time)
{
return ($time === null) ? $this->getTimeProvider()->getTime() : $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)
public function getQRText($label, $secret)
{
return 'otpauth://totp/' . rawurlencode($label)
. '?secret=' . rawurlencode($secret)
@@ -168,27 +176,76 @@ class TwoFactorAuth
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
. '&digits=' . intval($this->digits);
}
private function base32Decode($value)
{
if (strlen($value)==0) return '';
if (preg_match('/[^'.preg_quote(self::$_base32dict).']/', $value) !== 0)
throw new TwoFactorAuthException('Invalid base32 string');
$buffer = '';
foreach (str_split($value) as $char)
foreach (str_split($value) as $char)
{
if ($char !== '=')
$buffer .= str_pad(decbin(self::$_base32lookup[$char]), 5, 0, STR_PAD_LEFT);
}
$length = strlen($buffer);
$blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
$output = '';
foreach (explode(' ', $blocks) as $block)
$output .= chr(bindec(str_pad($block, 8, 0, STR_PAD_RIGHT)));
return $output;
}
}
/**
* @return IQRCodeProvider
* @throws TwoFactorAuthException
*/
public function getQrCodeProvider()
{
// Set default QR Code provider if none was specified
if (null === $this->qrcodeprovider) {
return $this->qrcodeprovider = new Providers\Qr\GoogleQRCodeProvider();
}
return $this->qrcodeprovider;
}
/**
* @return IRNGProvider
* @throws TwoFactorAuthException
*/
public function getRngprovider()
{
if (null !== $this->rngprovider) {
return $this->rngprovider;
}
if (function_exists('random_bytes')) {
return $this->rngprovider = new Providers\Rng\CSRNGProvider();
}
if (function_exists('mcrypt_create_iv')) {
return $this->rngprovider = new Providers\Rng\MCryptRNGProvider();
}
if (function_exists('openssl_random_pseudo_bytes')) {
return $this->rngprovider = new Providers\Rng\OpenSSLRNGProvider();
}
if (function_exists('hash')) {
return $this->rngprovider = new Providers\Rng\HashRNGProvider();
}
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
}
/**
* @return ITimeProvider
* @throws TwoFactorAuthException
*/
public function getTimeProvider()
{
// Set default time provider if none was specified
if (null === $this->timeprovider) {
return $this->timeprovider = new Providers\Time\LocalMachineTimeProvider();
}
return $this->timeprovider;
}
}
+3 -7
View File
@@ -2,10 +2,6 @@
namespace RobThree\Auth;
class TwoFactorAuthException extends \Exception
{
function __construct($message = "", $code = 0, $exception = null)
{
parent::__construct($message, $code, $exception);
}
}
use Exception;
class TwoFactorAuthException extends \Exception {}
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./lib</directory>
</whitelist>
</filter>
</phpunit>
View File
+99 -36
View File
@@ -5,6 +5,7 @@ require_once 'lib/TwoFactorAuthException.php';
require_once 'lib/Providers/Qr/IQRCodeProvider.php';
require_once 'lib/Providers/Qr/BaseHTTPQRCodeProvider.php';
require_once 'lib/Providers/Qr/GoogleQRCodeProvider.php';
require_once 'lib/Providers/Qr/QRException.php';
require_once 'lib/Providers/Rng/IRNGProvider.php';
require_once 'lib/Providers/Rng/RNGException.php';
@@ -12,10 +13,18 @@ require_once 'lib/Providers/Rng/CSRNGProvider.php';
require_once 'lib/Providers/Rng/MCryptRNGProvider.php';
require_once 'lib/Providers/Rng/OpenSSLRNGProvider.php';
require_once 'lib/Providers/Rng/HashRNGProvider.php';
require_once 'lib/Providers/Rng/RNGException.php';
require_once 'lib/Providers/Time/ITimeProvider.php';
require_once 'lib/Providers/Time/LocalMachineTimeProvider.php';
require_once 'lib/Providers/Time/HttpTimeProvider.php';
require_once 'lib/Providers/Time/NTPTimeProvider.php';
require_once 'lib/Providers/Time/TimeException.php';
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
use RobThree\Auth\Providers\Rng\IRNGProvider;
use RobThree\Auth\Providers\Time\ITimeProvider;
class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
@@ -44,22 +53,6 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
new TwoFactorAuth('Test', 6, 30, 'xxx');
}
/**
* @expectedException \RobThree\Auth\TwoFactorAuthException
*/
public function testConstructorThrowsOnQrProviderNotImplementingInterface() {
new TwoFactorAuth('Test', 6, 30, 'sha1', new stdClass());
}
/**
* @expectedException \RobThree\Auth\TwoFactorAuthException
*/
public function testConstructorThrowsOnRngProviderNotImplementingInterface() {
new TwoFactorAuth('Test', 6, 30, 'sha1', null, new stdClass());
}
public function testGetCodeReturnsCorrectResults() {
$tfa = new TwoFactorAuth('Test');
@@ -103,6 +96,41 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
}
public function testEnsureCorrectTimeDoesNotThrowForCorrectTime() {
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(128);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
}
/**
* @expectedException \RobThree\Auth\TwoFactorAuthException
*/
public function testEnsureCorrectTimeThrowsOnIncorrectTime() {
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(124);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
$tfa->ensureCorrectTime(array($tpr2), 0); // We force a leniency of 0, 124-123 = 1 so this should throw
}
public function testEnsureDefaultTimeProviderReturnsCorrectTime() {
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
$tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1); // Use a leniency of 1, should the time change between both time() calls
}
public function testEnsureAllTimeProvidersReturnCorrectTime() {
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
$tfa->ensureCorrectTime(array(
new RobThree\Auth\Providers\Time\NTPTimeProvider(), // Uses pool.ntp.org by default
//new RobThree\Auth\Providers\Time\NTPTimeProvider('time.google.com'), // Somehow time.google.com and time.windows.com make travis timeout??
new RobThree\Auth\Providers\Time\HttpTimeProvider(), // Uses google.com by default
new RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'),
new RobThree\Auth\Providers\Time\HttpTimeProvider('https://yahoo.com'),
));
}
public function testVerifyCodeWorksCorrectly() {
@@ -123,6 +151,27 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals(true , $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
}
public function testVerifyCorrectTimeSliceIsReturned() {
$tfa = new TwoFactorAuth('Test', 6, 30);
// We test with discrepancy 3 (so total of 7 codes: c-3, c-2, c-1, c, c+1, c+2, c+3
// Ensure each corresponding timeslice is returned correctly
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
$this->assertEquals(47561570, $timeslice1);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
$this->assertEquals(47561571, $timeslice2);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
$this->assertEquals(47561572, $timeslice3);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
$this->assertEquals(47561573, $timeslice4);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
$this->assertEquals(47561574, $timeslice5);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
$this->assertEquals(47561575, $timeslice6);
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
$this->assertEquals(47561576, $timeslice7);
}
public function testTotpUriIsCorrect() {
$qr = new TestQrProvider();
@@ -138,7 +187,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
*/
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize() {
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', $qr);
$tfa->getQRCodeImageAsDataUri('Test', 'VMR466AB62ZBOKHE', 0);
}
@@ -150,7 +199,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$tfa = new TwoFactorAuth('Test');
$tfa->getCode('FOO1BAR8BAZ9'); //1, 8 & 9 are invalid chars
}
/**
* @expectedException \RobThree\Auth\TwoFactorAuthException
*/
@@ -158,7 +207,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$tfa = new TwoFactorAuth('Test');
$tfa->getCode('mzxw6==='); //Lowercase
}
public function testKnownBase32DecodeTestVectors() {
// We usually don't test internals (e.g. privates) but since we rely heavily on base32 decoding and don't want
// to expose this method nor do we want to give people the possibility of implementing / providing their own base32
@@ -167,15 +216,15 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
// be any bugs hiding in there. We **could** 'fool' ourselves by calling the public getCode() method (which uses
// base32decode internally) and then make sure getCode's output (in digits) equals expected output since that would
// mean the base32Decode() works as expected but that **could** hide some subtle bug(s) in decoding the base32 string.
// "In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't
// expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods."
// Dave Thomas and Andy Hunt -- "Pragmatic Unit Testing
$tfa = new TwoFactorAuth('Test');
$method = new ReflectionMethod('RobThree\Auth\TwoFactorAuth', 'base32Decode');
$method->setAccessible(true);
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
$this->assertEquals('', $method->invoke($tfa, ''));
$this->assertEquals('f', $method->invoke($tfa, 'MY======'));
@@ -185,17 +234,17 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
}
public function testKnownBase32DecodeUnpaddedTestVectors() {
// See testKnownBase32DecodeTestVectors() for the rationale behind testing the private base32Decode() method.
// This test ensures that strings without the padding-char ('=') are also decoded correctly.
// https://tools.ietf.org/html/rfc4648#page-4:
// https://tools.ietf.org/html/rfc4648#page-4:
// "In some circumstances, the use of padding ("=") in base-encoded data is not required or used."
$tfa = new TwoFactorAuth('Test');
$method = new ReflectionMethod('RobThree\Auth\TwoFactorAuth', 'base32Decode');
$method->setAccessible(true);
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
$this->assertEquals('', $method->invoke($tfa, ''));
$this->assertEquals('f', $method->invoke($tfa, 'MY'));
@@ -218,7 +267,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals('69279037', $tfa->getCode($secret, 2000000000));
$this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
}
public function testKnownTestVectors_sha256() {
//Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
@@ -230,7 +279,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals('90698825', $tfa->getCode($secret, 2000000000));
$this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
}
public function testKnownTestVectors_sha512() {
//Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
@@ -263,15 +312,17 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
$this->assertEquals(false, $rng->isCryptographicallySecure());
}
/**
* @requires function mcrypt_create_iv
*/
public function testMCryptRNGProvidersReturnExpectedNumberOfBytes() {
$rng = new \RobThree\Auth\Providers\Rng\MCryptRNGProvider();
foreach ($this->getRngTestLengths() as $l)
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
$this->assertEquals(true, $rng->isCryptographicallySecure());
if (function_exists('mcrypt_create_iv')) {
$rng = new \RobThree\Auth\Providers\Rng\MCryptRNGProvider();
foreach ($this->getRngTestLengths() as $l)
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
$this->assertEquals(true, $rng->isCryptographicallySecure());
}
}
/**
@@ -313,11 +364,11 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
class TestRNGProvider implements IRNGProvider {
private $isSecure;
function __construct($isSecure = false) {
$this->isSecure = $isSecure;
}
public function getRandomBytes($bytecount) {
$result = '';
for ($i=0; $i<$bytecount; $i++)
@@ -325,7 +376,7 @@ class TestRNGProvider implements IRNGProvider {
return $result;
}
public function isCryptographicallySecure() {
return $this->isSecure;
}
@@ -339,4 +390,16 @@ class TestQrProvider implements IQRCodeProvider {
public function getMimeType() {
return 'test/test';
}
}
class TestTimeProvider implements ITimeProvider {
private $time;
function __construct($time) {
$this->time = $time;
}
public function getTime() {
return $this->time;
}
}