diff options
author | Christian Riesen <chris.riesen@gmail.com> | 2016-06-09 20:33:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-09 20:33:09 +0200 |
commit | 75754f4a452e1e0fb158e483306ac16198695a73 (patch) | |
tree | f0f4f9925b807ea0ac0406c82d963ffd86d6e5d2 | |
parent | 1fe52a904a61a9b9613ab7d15089b4c9aeaba383 (diff) | |
parent | 1677bddb742b5c89ee310ce99164263bb33b3112 (diff) | |
download | otp-75754f4a452e1e0fb158e483306ac16198695a73.zip otp-75754f4a452e1e0fb158e483306ac16198695a73.tar.gz otp-75754f4a452e1e0fb158e483306ac16198695a73.tar.bz2 |
Merge pull request #12 from therealssj/hotp-counter2.1.0
Add hotp counter window
-rw-r--r-- | src/Otp.php | 26 | ||||
-rw-r--r-- | src/OtpInterface.php | 12 | ||||
-rw-r--r-- | tests/OtpTest.php | 161 |
3 files changed, 175 insertions, 24 deletions
diff --git a/src/Otp.php b/src/Otp.php index ed9b671..1df9782 100644 --- a/src/Otp.php +++ b/src/Otp.php @@ -62,8 +62,8 @@ class Otp implements OtpInterface */ public function hotp($secret, $counter) { - if (!is_numeric($counter)) { - throw new \InvalidArgumentException('Counter must be integer'); + if (!is_numeric($counter) || $counter < 0) { + throw new \InvalidArgumentException('Invalid counter supplied'); } $hash = hash_hmac( @@ -95,6 +95,28 @@ class Otp implements OtpInterface { return $this->safeCompare($this->hotp($secret, $counter), $key); } + + + /* (non-PHPdoc) + * @see Otp.OtpInterface::checkHotpResync() + */ + public function checkHotpResync($secret, $counter, $key, $counterwindow = 2) + { + if (!is_numeric($counter) || $counter < 0) { + throw new \InvalidArgumentException('Invalid counter supplied'); + } + + if(!is_numeric($counterwindow) || $counterwindow < 0){ + throw new \InvalidArgumentException('Invalid counterwindow supplied'); + } + + for($c = 0; $c <= $counterwindow; $c = $c + 1) { + if($this->safeCompare($this->hotp($secret, $counter + $c), $key)){ + return $counter + $c; + } + } + return false; + } /* (non-PHPdoc) * @see Otp.OtpInterface::checkTotp() diff --git a/src/OtpInterface.php b/src/OtpInterface.php index 62e60c8..e241d5d 100644 --- a/src/OtpInterface.php +++ b/src/OtpInterface.php @@ -53,6 +53,18 @@ interface OtpInterface function checkHotp($secret, $counter, $key); /** + * Checks Hotp against a key for a provided counter window + * + * @param string $secret Base32 Secret String + * @param integer $counter Counter + * @param string $key User supplied key + * @param integer $counterwindow (optional) Size of the look-ahead window. Default value is 2 + * + * @return int|boolean the counter if key is correct else false + */ + function checkHotpResync($secret, $counter, $key, $counterwindow = 2); + + /** * Checks Totp agains a key * * @param string $secret Base32 Secret String diff --git a/tests/OtpTest.php b/tests/OtpTest.php index fe606e2..1c9affc 100644 --- a/tests/OtpTest.php +++ b/tests/OtpTest.php @@ -37,27 +37,85 @@ class OtpTest extends \PHPUnit_Framework_TestCase parent::tearDown(); } + + /** + * Invalid counter values for tests + */ + public function hotpTestValues() + { + return [ + ['755224', 0], ['287082', 1], ['359152', 2], + ['969429', 3], ['338314', 4], ['254676', 5], + ['287922', 6], ['162583', 7], ['399871', 8], + ['520489', 9] + ]; + } + + /** + * Invalid counter values for tests + */ + public function totpTestValues() + { + return [ + ['94287082', 59], ['07081804', 1111111109], ['14050471', 1111111111], + ['89005924', 1234567890], ['69279037', 2000000000], ['65353130', 20000000000], + ]; + } + + /** + * Invalid counter values for tests + */ + public function invalidCounterValues() + { + return [ + ['a'], [-1] + ]; + } + + /** + * Invalid counter values for tests + */ + public function hotpResyncDefaultTestValues() + { + return [ + ['755224', 0], ['287082', 1], ['359152', 2] + ]; + } + + /** + * Invalid counter values for tests + */ + public function hotpResyncWindowTestValues() + { + return [ + ['969429', 0, 3], ['338314', 0, 4], + ['287922', 3, 3], ['162583', 3, 4] + ]; + } + + /** + * Invalid counter values for tests + */ + public function hotpResyncFailureTestValues() + { + return [ + ['287922', 7], ['162583', 8], ['399871', 9] + ]; + } /** * Tests Otp->hotp() * * Using test vectors from RFC * https://tools.ietf.org/html/rfc4226 + * + * @dataProvider hotpTestValues */ - public function testHotpRfc() + public function testHotpRfc($key, $counter) { $secret = $this->secret; - $this->assertEquals('755224', $this->Otp->hotp($secret, 0)); - $this->assertEquals('287082', $this->Otp->hotp($secret, 1)); - $this->assertEquals('359152', $this->Otp->hotp($secret, 2)); - $this->assertEquals('969429', $this->Otp->hotp($secret, 3)); - $this->assertEquals('338314', $this->Otp->hotp($secret, 4)); - $this->assertEquals('254676', $this->Otp->hotp($secret, 5)); - $this->assertEquals('287922', $this->Otp->hotp($secret, 6)); - $this->assertEquals('162583', $this->Otp->hotp($secret, 7)); - $this->assertEquals('399871', $this->Otp->hotp($secret, 8)); - $this->assertEquals('520489', $this->Otp->hotp($secret, 9)); + $this->assertEquals($key, $this->Otp->hotp($secret, $counter)); } /** @@ -67,8 +125,10 @@ class OtpTest extends \PHPUnit_Framework_TestCase * its own tests * Using test vectors from RFC * https://tools.ietf.org/html/rfc6238 + * + * @dataProvider totpTestValues */ - public function testTotpRfc() + public function testTotpRfc($key, $time) { $secret = $this->secret; @@ -79,12 +139,8 @@ class OtpTest extends \PHPUnit_Framework_TestCase // to count as the key // SHA 1 grouping - $this->assertEquals('94287082', $this->Otp->hotp($secret, floor(59/30)), 'sha1 with time 59'); - $this->assertEquals('07081804', $this->Otp->hotp($secret, floor(1111111109/30)), 'sha1 with time 1111111109'); - $this->assertEquals('14050471', $this->Otp->hotp($secret, floor(1111111111/30)), 'sha1 with time 1111111111'); - $this->assertEquals('89005924', $this->Otp->hotp($secret, floor(1234567890/30)), 'sha1 with time 1234567890'); - $this->assertEquals('69279037', $this->Otp->hotp($secret, floor(2000000000/30)), 'sha1 with time 2000000000'); - $this->assertEquals('65353130', $this->Otp->hotp($secret, floor(20000000000/30)), 'sha1 with time 20000000000'); + $this->assertEquals($key, $this->Otp->hotp($secret, floor($time/30)), "sha 1 with $time"); + /* The following tests do NOT pass. @@ -110,14 +166,75 @@ class OtpTest extends \PHPUnit_Framework_TestCase $this->assertEquals('47863826', $this->Otp->hotp($secret, floor(20000000000/30)), 'sha512 with time 20000000000'); */ } - + + /** + * @dataProvider invalidCounterValues + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Invalid counter supplied + */ + public function testHotpInvalidCounter($counter) + { + $this->Otp->hotp($this->secret, $counter); + } + + /** + * Tests Otp->checkHotpResync() with default counter window + * + * @dataProvider hotpResyncDefaultTestValues + */ + public function testHotpResyncDefault($key, $counter) + { + $secret = $this->secret; + + // test with default counter window + $this->assertSame($counter, $this->Otp->checkHotpResync($secret, $counter, $key)); + } + + + /** + * Tests Otp->checkHotpResync() with a provided counter window + * + * @dataProvider hotpResyncWindowTestValues + */ + public function testHotpResyncWindow($key, $counter, $counterwindow) + { + $secret = $this->secret; + + // test with provided counter window + $this->assertSame(($counter + $counterwindow), $this->Otp->checkHotpResync($secret, $counter, $key, $counterwindow)); + } + + /** + * Tests Otp->checkHotpResync() with mismatching key and counter + * + * @dataProvider hotpResyncFailureTestValues + */ + public function testHotpResyncFailures($key, $counter) + { + $secret = $this->secret; + + // test failures + $this->assertFalse($this->Otp->checkHotpResync($secret, $counter, $key)); + } + + /** + * @dataProvider invalidCounterValues + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Invalid counter supplied + */ + public function testHotpResyncInvalidCounter($counter) + { + $this->Otp->checkHotpResync($this->secret, $counter, '755224'); + } + /** + * @dataProvider invalidCounterValues * @expectedException InvalidArgumentException - * @expectedExceptionMessage Counter must be integer + * @expectedExceptionMessage Invalid counterwindow supplied */ - public function testHotpInvalidCounter() + public function testHotpResyncInvalidCounterWindow($counterwindow) { - $this->Otp->hotp($this->secret, 'a'); + $this->Otp->checkHotpResync($this->secret, 0, '755224', $counterwindow); } } |