From 580feecdbfa0dac342d133fa1482f7a4bd4539fb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Julius=20H=C3=A4rtl?= Date: Sat, 4 Feb 2023 22:35:35 +0100 Subject: [PATCH] fix(authtoken): Store only one hash for authtokens with the current password per user MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../Token/PublicKeyTokenMapper.php | 27 +++++++++++++++++++ .../Token/PublicKeyTokenProvider.php | 24 ++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php index 7b11ef8adf3..8feb275b3b7 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php +++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php @@ -229,4 +229,31 @@ class PublicKeyTokenMapper extends QBMapper { ); $update->executeStatement(); } + + public function updateHashesForUser(string $userId, string $passwordHash): void { + $qb = $this->db->getQueryBuilder(); + $update = $qb->update($this->getTableName()) + ->set('password_hash', $qb->createNamedParameter($passwordHash)) + ->where( + $qb->expr()->eq('uid', $qb->createNamedParameter($userId)) + ); + $update->executeStatement(); + } + + public function getFirstTokenForUser(string $userId): ?PublicKeyToken { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId))) + ->setMaxResults(1) + ->orderBy('id'); + $result = $qb->executeQuery(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + return null; + } + return PublicKeyToken::fromRow($data); + } } diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index c8adec24b31..e64abcd231f 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -102,9 +102,23 @@ class PublicKeyTokenProvider implements IProvider { $name = mb_substr($name, 0, 120) . '…'; } + // We need to check against one old token to see if there is a password + // hash that we can reuse for detecting outdated passwords + $randomOldToken = $this->mapper->getFirstTokenForUser($uid); + $oldTokenMatches = $randomOldToken && $this->hasher->verify(sha1($password) . $password, $randomOldToken->getPasswordHash()); + $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember); + + if ($oldTokenMatches) { + $dbToken->setPasswordHash($randomOldToken->getPasswordHash()); + } + $this->mapper->insert($dbToken); + if (!$oldTokenMatches && $password !== null) { + $this->updatePasswords($uid, $password); + } + // Add the token to the cache $this->cache[$dbToken->getToken()] = $dbToken; @@ -289,10 +303,11 @@ class PublicKeyTokenProvider implements IProvider { // Update the password for all tokens $tokens = $this->mapper->getTokenByUser($token->getUID()); + $hashedPassword = $this->hashPassword($password); foreach ($tokens as $t) { $publicKey = $t->getPublicKey(); $t->setPassword($this->encryptPassword($password, $publicKey)); - $t->setPasswordHash($this->hashPassword($password)); + $t->setPasswordHash($hashedPassword); $this->updateToken($t); } } @@ -481,6 +496,13 @@ class PublicKeyTokenProvider implements IProvider { $this->updateToken($t); } } + + // If password hashes are different we update them all to be equal so + // that the next execution only needs to verify once + if (count($hashNeedsUpdate) > 1) { + $newPasswordHash = $this->hashPassword($password); + $this->mapper->updateHashesForUser($uid, $newPasswordHash); + } } private function logOpensslError() { -- 2.39.5