Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/engine/PhabricatorAuthCSRFEngine.php
12256 views
1
<?php
2
3
final class PhabricatorAuthCSRFEngine extends Phobject {
4
5
private $salt;
6
private $secret;
7
8
public function setSalt($salt) {
9
$this->salt = $salt;
10
return $this;
11
}
12
13
public function getSalt() {
14
return $this->salt;
15
}
16
17
public function setSecret(PhutilOpaqueEnvelope $secret) {
18
$this->secret = $secret;
19
return $this;
20
}
21
22
public function getSecret() {
23
return $this->secret;
24
}
25
26
public function newSalt() {
27
$salt_length = $this->getSaltLength();
28
return Filesystem::readRandomCharacters($salt_length);
29
}
30
31
public function newToken() {
32
$salt = $this->getSalt();
33
34
if (!$salt) {
35
throw new PhutilInvalidStateException('setSalt');
36
}
37
38
$token = $this->newRawToken($salt);
39
$prefix = $this->getBREACHPrefix();
40
41
return sprintf('%s%s%s', $prefix, $salt, $token);
42
}
43
44
public function isValidToken($token) {
45
$salt_length = $this->getSaltLength();
46
47
// We expect a BREACH-mitigating token. See T3684.
48
$breach_prefix = $this->getBREACHPrefix();
49
$breach_prelen = strlen($breach_prefix);
50
if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) {
51
return false;
52
}
53
54
$salt = substr($token, $breach_prelen, $salt_length);
55
$token = substr($token, $breach_prelen + $salt_length);
56
57
foreach ($this->getWindowOffsets() as $offset) {
58
$expect_token = $this->newRawToken($salt, $offset);
59
if (phutil_hashes_are_identical($expect_token, $token)) {
60
return true;
61
}
62
}
63
64
return false;
65
}
66
67
private function newRawToken($salt, $offset = 0) {
68
$now = PhabricatorTime::getNow();
69
$cycle_frequency = $this->getCycleFrequency();
70
71
$time_block = (int)floor($now / $cycle_frequency);
72
$time_block = $time_block + $offset;
73
74
$secret = $this->getSecret();
75
if (!$secret) {
76
throw new PhutilInvalidStateException('setSecret');
77
}
78
$secret = $secret->openEnvelope();
79
80
$hash = PhabricatorHash::digestWithNamedKey(
81
$secret.$time_block.$salt,
82
'csrf');
83
84
return substr($hash, 0, $this->getTokenLength());
85
}
86
87
private function getBREACHPrefix() {
88
return 'B@';
89
}
90
91
private function getSaltLength() {
92
return 8;
93
}
94
95
private function getTokenLength() {
96
return 16;
97
}
98
99
private function getCycleFrequency() {
100
return phutil_units('1 hour in seconds');
101
}
102
103
private function getWindowOffsets() {
104
// We accept some tokens from the recent past and near future. Users may
105
// have older tokens if they close their laptop and open it up again
106
// later. Users may have newer tokens if there are multiple web hosts with
107
// a bit of clock skew.
108
109
// Javascript on the client tries to keep CSRF tokens up to date, but
110
// it may fail, and it doesn't run if the user closes their laptop.
111
112
// The window during which our tokens remain valid is generally more
113
// conservative than other platforms. For example, Rails uses "session
114
// duration" and Django uses "forever".
115
116
return range(-6, 1);
117
}
118
119
}
120
121