-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTotpEnroll.php
More file actions
147 lines (135 loc) · 4.23 KB
/
Copy pathTotpEnroll.php
File metadata and controls
147 lines (135 loc) · 4.23 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
<?php
declare(strict_types=1);
namespace FediE2EE\PKDServer\RequestHandlers\Api;
use DateMalformedStringException;
use DateTime;
use FediE2EE\PKD\Crypto\Exceptions\{
CryptoException,
JsonException,
NotImplementedException
};
use FediE2EE\PKD\Crypto\Protocol\HPKEAdapter;
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 TotpEnroll 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/enroll")]
#[Override]
public function handle(ServerRequestInterface $request): ResponseInterface
{
try {
$parsed = $this->parseTotpBody(
$request->getBody()->getContents(),
'enrollment',
['actor-id', 'key-id', 'otp-current', 'otp-previous', '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/enroll',
'TOTP-Enroll',
);
} catch (ProtocolException $ex) {
return $this->error($ex->getMessage(), 400);
}
$hpke = $this->config()->getHPKE();
$decryptedSecret = (new HPKEAdapter($hpke->cs, 'fedi-e2ee:v1/api/totp/enroll'))->open(
$hpke->getDecapsKey(),
$hpke->getEncapsKey(),
Base32::decode($sub['totp-secret']),
);
$tsCurrent = self::verifyTOTP($decryptedSecret, $sub['otp-current']);
$tsPrevious = self::verifyTOTP($decryptedSecret, $sub['otp-previous']);
if (is_null($tsCurrent) || is_null($tsPrevious)) {
return $this->error('Invalid TOTP codes', 406);
}
if ($tsCurrent <= $tsPrevious) {
return $this->error('TOTP codes must be increasing', 406);
}
if ($this->totpTable->getSecretByDomain($domain)) {
return $this->error('TOTP already enabled', 409);
}
$this->totpTable->saveSecret($domain, $decryptedSecret, $tsCurrent);
return $this->json([
'!pkd-context' => 'fedi-e2ee:v1/api/totp/enroll',
'success' => true,
'time' => (string) (new DateTime())->getTimestamp(),
]);
}
#[Override]
public function getEnabledRateLimits(): array
{
return ['ip', 'domain'];
}
}