forked from Spomky-Labs/otphp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
HOTPTest.php
181 lines (149 loc) · 4.97 KB
/
HOTPTest.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
<?php
declare(strict_types=1);
namespace OTPHP\Test;
use InvalidArgumentException;
use OTPHP\HOTP;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
/**
* @internal
*/
final class HOTPTest extends TestCase
{
#[Test]
public function labelNotDefined(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The label is not set.');
$hotp = HOTP::generate();
$hotp->getProvisioningUri();
}
#[Test]
public function issuerHasColon(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Issuer must not contain a colon.');
$otp->setIssuer('foo%3Abar');
}
#[Test]
public function issuerHasColon2(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Issuer must not contain a colon.');
$otp->setIssuer('foo%3abar');
}
#[Test]
public function labelHasColon(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Label must not contain a colon.');
$otp->setLabel('foo%3Abar');
}
#[Test]
public function labelHasColon2(): void
{
$otp = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Label must not contain a colon.');
$otp->setLabel('foo:bar');
}
#[Test]
public function digitsIsNot1OrMore(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Digits must be at least 1.');
$htop->setDigits(0);
}
#[Test]
public function counterIsNot1OrMore(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Counter must be at least 0.');
$htop->setCounter(-500);
}
#[Test]
public function digestIsNotSupported(): void
{
$htop = HOTP::createFromSecret('JDDK4U6G3BJLEZ7Y');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('The "foo" digest is not supported.');
$htop->setDigest('foo');
}
/**
* xpectedExceptionMessage.
*/
#[Test]
public function secretShouldBeBase32Encoded(): void
{
$otp = HOTP::createFromSecret(random_bytes(32));
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Unable to decode the secret. Is it correctly base32 encoded?');
$otp->at(0);
}
#[Test]
public function objectCreationValid(): void
{
$otp = HOTP::generate();
static::assertMatchesRegularExpression('/^[A-Z2-7]+$/', $otp->getSecret());
}
#[Test]
public function getProvisioningUri(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
$otp->setParameter('image', 'https://foo.bar/baz');
static::assertSame(
'otpauth://hotp/My%20Project%3Aalice%40foo.bar?counter=1000&digits=8&image=https%3A%2F%2Ffoo.bar%2Fbaz&issuer=My%20Project&secret=JDDK4U6G3BJLEZ7Y',
$otp->getProvisioningUri()
);
}
#[Test]
public function verifyCounterInvalid(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
static::assertFalse($otp->verify('98449994', 100));
}
#[Test]
public function verifyCounterChanged(): void
{
$otp = $this->createHOTP(8, 'sha1', 1100);
static::assertTrue($otp->verify('98449994'));
static::assertFalse($otp->verify('11111111', 1099));
static::assertSame($otp->getCounter(), 1101);
}
#[Test]
public function verifyValidInWindow(): void
{
$otp = $this->createHOTP(8, 'sha1', 1000);
static::assertTrue($otp->verify('59647237', 1000, 50));
static::assertFalse($otp->verify('59647237', 1000, 50));
static::assertFalse($otp->verify('59647237', 2000, 50));
}
/**
* @param non-empty-string $digest
* @param non-empty-string $secret
* @param non-empty-string $label
* @param non-empty-string $issuer
*/
private function createHOTP(
int $digits,
string $digest,
int $counter,
string $secret = 'JDDK4U6G3BJLEZ7Y',
string $label = '[email protected]',
string $issuer = 'My Project'
): HOTP {
$otp = HOTP::createFromSecret($secret);
$otp->setCounter($counter);
$otp->setDigest($digest);
$otp->setDigits($digits);
$otp->setLabel($label);
$otp->setIssuer($issuer);
return $otp;
}
}