-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTotpRotate.php
More file actions
154 lines (143 loc) · 4.59 KB
/
Copy pathTotpRotate.php
File metadata and controls
154 lines (143 loc) · 4.59 KB
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
<?php
declare(strict_types=1);
namespace FediE2EE\PKDServer\RequestHandlers\Api;
use DateMalformedStringException;
use DateTime;
use FediE2EE\PKD\Crypto\Protocol\HPKEAdapter;
use FediE2EE\PKD\Crypto\Exceptions\{
CryptoException,
JsonException,
NotImplementedException
};
use FediE2EE\PKDServer\{
Exceptions\CacheException,
Exceptions\DependencyException,
Exceptions\ProtocolException,
Exceptions\TableException,
Interfaces\LimitingHandlerInterface,
Meta\Route,
Traits\ReqTrait,
Traits\TOTPTrait
};
use FediE2EE\PKDServer\Tables\TOTP as TOTPTable;
use JsonException as BaseJsonException;
use Override;
use ParagonIE\CipherSweet\Exception\{
ArrayKeyException,
BlindIndexNotFoundException,
CipherSweetException,
CryptoOperationException,
InvalidCiphertextException
};
use ParagonIE\ConstantTime\Base32;
use ParagonIE\HPKE\HPKEException;
use Random\RandomException;
use Psr\Http\Message\{
ResponseInterface,
ServerRequestInterface
};
use Psr\Http\Server\RequestHandlerInterface;
use SodiumException;
use Throwable;
use TypeError;
use function is_null;
class TotpRotate implements RequestHandlerInterface, LimitingHandlerInterface
{
use ReqTrait;
use TOTPTrait;
protected TOTPTable $totpTable;
/**
* @throws DependencyException
* @throws TableException
* @throws CacheException
*/
public function __construct()
{
$totpTable = $this->table('TOTP');
if (!($totpTable instanceof TOTPTable)) {
throw new TypeError('Could not load TOTP table at runtime');
}
$this->totpTable = $totpTable;
}
/**
* @throws ArrayKeyException
* @throws BaseJsonException
* @throws BlindIndexNotFoundException
* @throws CacheException
* @throws CipherSweetException
* @throws CryptoException
* @throws CryptoOperationException
* @throws DateMalformedStringException
* @throws DependencyException
* @throws HPKEException
* @throws InvalidCiphertextException
* @throws JsonException
* @throws NotImplementedException
* @throws RandomException
* @throws SodiumException
* @throws TableException
*/
#[Route("/api/totp/rotate")]
#[Override]
public function handle(ServerRequestInterface $request): ResponseInterface
{
try {
$parsed = $this->parseTotpBody(
$request->getBody()->getContents(),
'rotation',
['actor-id', 'key-id', 'old-otp', 'new-otp-current', 'new-otp-previous', 'new-totp-secret'],
);
} catch (Throwable) {
return $this->error('Missing required fields', 400);
}
$body = $parsed['body'];
$sub = $parsed['sub'];
try {
$domain = $this->validateTotpRequest(
$body,
$sub['actor-id'],
$sub['key-id'],
'fedi-e2ee:v1/api/totp/rotate',
'TOTP-Rotate',
);
} catch (ProtocolException $ex) {
return $this->error($ex->getMessage(), 400);
}
$oldTotp = $this->totpTable->getTotpByDomain($domain);
if (!$oldTotp) {
return $this->error('TOTP not enabled', 400);
}
$tsOld = $this->verifyTOTP($oldTotp['secret'], $sub['old-otp']);
if (is_null($tsOld)) {
return $this->error('Invalid old TOTP code', 403);
}
if ($tsOld <= $oldTotp['last_time_step']) {
return $this->error('Old TOTP code already used', 403);
}
$hpke = $this->config()->getHPKE();
$newSecret = (new HPKEAdapter($hpke->cs, 'fedi-e2ee:v1/api/totp/rotate'))->open(
$hpke->getDecapsKey(),
$hpke->getEncapsKey(),
Base32::decode($sub['new-totp-secret'])
);
$tsNewCurrent = $this->verifyTOTP($newSecret, $sub['new-otp-current']);
$tsNewPrevious = $this->verifyTOTP($newSecret, $sub['new-otp-previous']);
if (is_null($tsNewCurrent) || is_null($tsNewPrevious)) {
return $this->error('Invalid new TOTP codes', 406);
}
if ($tsNewCurrent <= $tsNewPrevious) {
return $this->error('New TOTP codes must be increasing', 406);
}
$this->totpTable->updateSecret($domain, $newSecret, $tsNewCurrent);
return $this->json([
'!pkd-context' => 'fedi-e2ee:v1/api/totp/rotate',
'success' => true,
'time' => (string) (new DateTime())->getTimestamp(),
]);
}
#[Override]
public function getEnabledRateLimits(): array
{
return ['ip', 'domain'];
}
}