Обновление клиента
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
language: php
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- "$HOME/.composer/cache/files"
|
||||
|
||||
before_install:
|
||||
- php --info
|
||||
- composer self-update
|
||||
- composer install
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "rullzer/easytotp",
|
||||
"type": "library",
|
||||
"description": "Time-Based One-Time Password according to RFC6238",
|
||||
"keywords": ["otp","totp","googleauthenticator","rfc6238"],
|
||||
"homepage": "https://github.com/rullzer/easytotp",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roeland Jago Douma",
|
||||
"email": "roeland@famdouma.nl",
|
||||
"homepage": "http://rullzer.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"EasyTOTP\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"EasyTOTP\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="EasyTOTP Test Suite">
|
||||
<directory suffix="Test.php">tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<logging>
|
||||
<log type="coverage-html" target="build/coverage" title="Otp"
|
||||
charset="UTF-8" yui="true" highlight="true"
|
||||
lowUpperBound="35" highLowerBound="70"/>
|
||||
<log type="coverage-clover" target="build/logs/clover.xml"/>
|
||||
<log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
|
||||
</logging>
|
||||
</phpunit>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
class Factory {
|
||||
|
||||
public static function getTOTP(string $secret, int $timeStep = 30, int $digits = 6, int $offset = 0, string $hash = TOTPInterface::HASH_SHA1): TOTPInterface {
|
||||
return new TOTP(
|
||||
$secret,
|
||||
$timeStep,
|
||||
$digits,
|
||||
$offset,
|
||||
$hash,
|
||||
new TimeService()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
class TOTP implements TOTPInterface {
|
||||
|
||||
/** @var string */
|
||||
private $secret;
|
||||
/** @var int */
|
||||
private $digits;
|
||||
/** @var int */
|
||||
private $offset;
|
||||
/** @var int */
|
||||
private $timeStep;
|
||||
/** @var string */
|
||||
private $hashFunction;
|
||||
/** @var TimeService */
|
||||
private $timeService;
|
||||
|
||||
public function __construct(string $secret, int $timeStep, int $digits, int $offset, string $hashFunction, TimeService $timeService) {
|
||||
$this->secret = $secret;
|
||||
$this->timeStep = $timeStep;
|
||||
$this->digits = $digits;
|
||||
$this->offset = $offset;
|
||||
$this->hashFunction = $hashFunction;
|
||||
$this->timeService = $timeService;
|
||||
}
|
||||
|
||||
public function verify(string $otp, int $drift = 1, ?int $lastKnownCounter = null): TOTPResultInterface {
|
||||
$currentCounter = $this->getCurrentCounter();
|
||||
|
||||
$start = $currentCounter - $drift;
|
||||
$end = $currentCounter + $drift;
|
||||
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
// Skip counters smaller than the minimum
|
||||
if ($lastKnownCounter !== null && $i <= $lastKnownCounter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hash_equals($this->hotp($i), $otp)) {
|
||||
return new TOTPValidResult(
|
||||
$i,
|
||||
$i - $currentCounter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new TOTPInvalidResult();
|
||||
}
|
||||
|
||||
public function getDigits(): int {
|
||||
return $this->digits;
|
||||
}
|
||||
|
||||
public function getHashFunction(): string {
|
||||
return $this->hashFunction;
|
||||
}
|
||||
|
||||
public function getOffset(): int {
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function getSecret(): string {
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function getTimeStep(): int {
|
||||
return $this->timeStep;
|
||||
}
|
||||
|
||||
private function binaryCounter(int $counter): string {
|
||||
if (PHP_INT_SIZE === 4) {
|
||||
/*
|
||||
* Manually do 64bit magic
|
||||
* This will do boom in 2038 ;)
|
||||
*/
|
||||
return pack('N*', 0) . pack('N*', $counter);
|
||||
}
|
||||
|
||||
return pack('J', $counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://tools.ietf.org/html/rfc4226#section-5
|
||||
*/
|
||||
private function hotp(int $counter): string {
|
||||
$hash = hash_hmac(
|
||||
$this->hashFunction,
|
||||
$this->binaryCounter($counter),
|
||||
$this->secret,
|
||||
true
|
||||
);
|
||||
|
||||
return str_pad((string)$this->truncate($hash), $this->digits, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
private function truncate(string $hash): int {
|
||||
$offset = \ord($hash[strlen($hash)-1]) & 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)
|
||||
) % (10 ** $this->digits);
|
||||
}
|
||||
|
||||
private function getCurrentCounter(): int {
|
||||
return (int)floor(($this->timeService->getTime() + $this->offset) / $this->timeStep);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
interface TOTPInterface {
|
||||
|
||||
public const HASH_SHA1 = 'sha1';
|
||||
public const HASH_SHA256 = 'sha256';
|
||||
public const HASH_SHA512 = 'sha512';
|
||||
|
||||
/**
|
||||
* @param string $otp The one time password to verify
|
||||
* @param int $drift How many windows to look back and ahead
|
||||
* @param int $lastKnownCounter The last known counter value (can be obtained from TOTPValidResultInterface). This
|
||||
* avoid reuse of the same token (which is forbidden by the RFC.
|
||||
* @return mixed
|
||||
*/
|
||||
public function verify(string $otp, int $drift = 1, ?int $lastKnownCounter = null): TOTPResultInterface;
|
||||
|
||||
public function getDigits(): int;
|
||||
|
||||
public function getHashFunction(): string;
|
||||
|
||||
public function getOffset(): int;
|
||||
|
||||
public function getSecret(): string;
|
||||
|
||||
public function getTimeStep(): int;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
class TOTPInvalidResult implements TOTPInvalidResultInterface {
|
||||
public function valid(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
interface TOTPInvalidResultInterface extends TOTPResultInterface {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
interface TOTPResultInterface {
|
||||
|
||||
public function valid(): bool;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
class TOTPValidResult implements TOTPValidResultInterface {
|
||||
|
||||
/** @var int */
|
||||
private $counter;
|
||||
|
||||
/** @var int */
|
||||
private $drift;
|
||||
|
||||
public function __construct(int $counter, int $drift) {
|
||||
$this->counter = $counter;
|
||||
$this->drift = $drift;
|
||||
}
|
||||
|
||||
public function valid(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCounter(): int {
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
public function getDrift(): int {
|
||||
return $this->drift;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
interface TOTPValidResultInterface extends TOTPResultInterface {
|
||||
public function getCounter(): int;
|
||||
|
||||
public function getDrift(): int;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP;
|
||||
|
||||
class TimeService {
|
||||
public function getTime(): int {
|
||||
return time();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace EasyTOTP\Tests;
|
||||
|
||||
use EasyTOTP\TimeService;
|
||||
use EasyTOTP\TOTP;
|
||||
use EasyTOTP\TOTPInterface;
|
||||
use EasyTOTP\TOTPInvalidResultInterface;
|
||||
use EasyTOTP\TOTPValidResultInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Otp test case.
|
||||
*/
|
||||
class TOTPTest extends TestCase
|
||||
{
|
||||
/** @var string */
|
||||
private $secret = "12345678901234567890";
|
||||
|
||||
/** @var TimeService|MockObject */
|
||||
private $timeService;
|
||||
|
||||
/**
|
||||
* Prepares the environment before running a test.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->timeService = $this->createMock(TimeService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid counter values for tests
|
||||
*/
|
||||
public function totpTestValues()
|
||||
{
|
||||
return [
|
||||
[59, '94287082', TOTPInterface::HASH_SHA1, 1],
|
||||
[59, '46119246', TOTPInterface::HASH_SHA256, 1],
|
||||
[59, '90693936', TOTPInterface::HASH_SHA512, 1],
|
||||
[1111111109, '07081804', TOTPInterface::HASH_SHA1, 37037036],
|
||||
[1111111109, '68084774', TOTPInterface::HASH_SHA256, 37037036],
|
||||
[1111111109, '25091201', TOTPInterface::HASH_SHA512, 37037036],
|
||||
[1111111111, '14050471', TOTPInterface::HASH_SHA1, 37037037],
|
||||
[1111111111, '67062674', TOTPInterface::HASH_SHA256, 37037037],
|
||||
[1111111111, '99943326', TOTPInterface::HASH_SHA512, 37037037],
|
||||
[1234567890, '89005924', TOTPInterface::HASH_SHA1, 41152263],
|
||||
[1234567890, '91819424', TOTPInterface::HASH_SHA256, 41152263],
|
||||
[1234567890, '93441116', TOTPInterface::HASH_SHA512, 41152263],
|
||||
[2000000000, '69279037', TOTPInterface::HASH_SHA1, 66666666],
|
||||
[2000000000, '90698825', TOTPInterface::HASH_SHA256, 66666666],
|
||||
[2000000000, '38618901', TOTPInterface::HASH_SHA512, 66666666],
|
||||
[20000000000, '65353130', TOTPInterface::HASH_SHA1, 666666666],
|
||||
[20000000000, '77737706', TOTPInterface::HASH_SHA256, 666666666],
|
||||
[20000000000, '47863826', TOTPInterface::HASH_SHA512, 666666666],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
*/
|
||||
public function testTOTPRFC(int $time, string $otp, string $hashFunction) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->prepareSecret($this->secret, $hashFunction), $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 0);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
*/
|
||||
public function testTimeDriftBack(int $time, string $otp, string $hashFunction) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time-30);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
* @param int $counter
|
||||
*/
|
||||
public function testTimeDriftForward(int $time, string $otp, string $hashFunction, int $counter) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time+30);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
$this->assertSame($counter, $result->getCounter());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
* @param int $counter
|
||||
*/
|
||||
public function testTimeDriftOnlyForwardFails(int $time, string $otp, string $hashFunction, int $counter) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1, $counter+1);
|
||||
$this->assertInstanceOf(TOTPInvalidResultInterface::class, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
* @param int $counter
|
||||
*/
|
||||
public function testTimeDriftOnlyForwardSuccess(int $time, string $otp, string $hashFunction, int $counter) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1, $counter-1);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
$this->assertSame($counter, $result->getCounter());
|
||||
$this->assertSame(0, $result->getDrift());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
* @param int $counter
|
||||
*/
|
||||
public function testTimeDriftClockSlow(int $time, string $otp, string $hashFunction, int $counter) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time+30);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
$this->assertSame($counter, $result->getCounter());
|
||||
$this->assertSame(-1, $result->getDrift());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider totpTestValues
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $otp
|
||||
* @param string $hashFunction
|
||||
* @param int $counter
|
||||
*/
|
||||
public function testTimeDriftClockFase(int $time, string $otp, string $hashFunction, int $counter) {
|
||||
$this->timeService->method('getTime')
|
||||
->willReturn($time-30);
|
||||
|
||||
$totp = new TOTP(
|
||||
$this->prepareSecret($this->secret, $hashFunction),
|
||||
30,
|
||||
8,
|
||||
0,
|
||||
$hashFunction,
|
||||
$this->timeService);
|
||||
|
||||
$result = $totp->verify($otp, 1);
|
||||
$this->assertInstanceOf(TOTPValidResultInterface::class, $result);
|
||||
$this->assertSame($counter, $result->getCounter());
|
||||
$this->assertSame(1, $result->getDrift());
|
||||
}
|
||||
|
||||
private function prepareSecret(string $secret, string $hashFunction) {
|
||||
if ($hashFunction === TOTPInterface::HASH_SHA1) {
|
||||
$secretLength = 20;
|
||||
} else if ($hashFunction === TOTPInterface::HASH_SHA256) {
|
||||
$secretLength = 32;
|
||||
} else if ($hashFunction === TOTPInterface::HASH_SHA512) {
|
||||
$secretLength = 64;
|
||||
} else {
|
||||
$this->fail('Invalid hash function');
|
||||
}
|
||||
|
||||
while(strlen($secret) < $secretLength) {
|
||||
$secret .= $secret;
|
||||
}
|
||||
|
||||
$secret = substr($secret, 0, $secretLength);
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
Reference in New Issue
Block a user