Path: blob/master/src/applications/auth/engine/PhabricatorAuthCSRFEngine.php
12256 views
<?php12final class PhabricatorAuthCSRFEngine extends Phobject {34private $salt;5private $secret;67public function setSalt($salt) {8$this->salt = $salt;9return $this;10}1112public function getSalt() {13return $this->salt;14}1516public function setSecret(PhutilOpaqueEnvelope $secret) {17$this->secret = $secret;18return $this;19}2021public function getSecret() {22return $this->secret;23}2425public function newSalt() {26$salt_length = $this->getSaltLength();27return Filesystem::readRandomCharacters($salt_length);28}2930public function newToken() {31$salt = $this->getSalt();3233if (!$salt) {34throw new PhutilInvalidStateException('setSalt');35}3637$token = $this->newRawToken($salt);38$prefix = $this->getBREACHPrefix();3940return sprintf('%s%s%s', $prefix, $salt, $token);41}4243public function isValidToken($token) {44$salt_length = $this->getSaltLength();4546// We expect a BREACH-mitigating token. See T3684.47$breach_prefix = $this->getBREACHPrefix();48$breach_prelen = strlen($breach_prefix);49if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) {50return false;51}5253$salt = substr($token, $breach_prelen, $salt_length);54$token = substr($token, $breach_prelen + $salt_length);5556foreach ($this->getWindowOffsets() as $offset) {57$expect_token = $this->newRawToken($salt, $offset);58if (phutil_hashes_are_identical($expect_token, $token)) {59return true;60}61}6263return false;64}6566private function newRawToken($salt, $offset = 0) {67$now = PhabricatorTime::getNow();68$cycle_frequency = $this->getCycleFrequency();6970$time_block = (int)floor($now / $cycle_frequency);71$time_block = $time_block + $offset;7273$secret = $this->getSecret();74if (!$secret) {75throw new PhutilInvalidStateException('setSecret');76}77$secret = $secret->openEnvelope();7879$hash = PhabricatorHash::digestWithNamedKey(80$secret.$time_block.$salt,81'csrf');8283return substr($hash, 0, $this->getTokenLength());84}8586private function getBREACHPrefix() {87return 'B@';88}8990private function getSaltLength() {91return 8;92}9394private function getTokenLength() {95return 16;96}9798private function getCycleFrequency() {99return phutil_units('1 hour in seconds');100}101102private function getWindowOffsets() {103// We accept some tokens from the recent past and near future. Users may104// have older tokens if they close their laptop and open it up again105// later. Users may have newer tokens if there are multiple web hosts with106// a bit of clock skew.107108// Javascript on the client tries to keep CSRF tokens up to date, but109// it may fail, and it doesn't run if the user closes their laptop.110111// The window during which our tokens remain valid is generally more112// conservative than other platforms. For example, Rails uses "session113// duration" and Django uses "forever".114115return range(-6, 1);116}117118}119120121