From adfe367106ccf1dd158099f10be021702297ebb5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 1 Dec 2022 18:06:58 +0100 Subject: [PATCH 1/2] PublickKeyTokenProvider: Fix password update routine with password hash Signed-off-by: Marcel Klehr --- .../Version25000Date20220905140840.php | 57 +++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../Authentication/Token/PublicKeyToken.php | 6 ++ .../Token/PublicKeyTokenProvider.php | 20 +++++-- .../Token/PublicKeyTokenProviderTest.php | 2 + 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 core/Migrations/Version25000Date20220905140840.php diff --git a/core/Migrations/Version25000Date20220905140840.php b/core/Migrations/Version25000Date20220905140840.php new file mode 100644 index 00000000000..6cda6132a7f --- /dev/null +++ b/core/Migrations/Version25000Date20220905140840.php @@ -0,0 +1,57 @@ + + * + * @author Marcel Klehr + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version25000Date20220905140840 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $authTokenTable = $schema->getTable('authtoken'); + if (!$authTokenTable->hasColumn('password_hash')) { + $authTokenTable->addColumn('password_hash', Types::STRING, [ + 'notnull' => false, + 'length' => 255, + ]); + return $schema; + } + return null; + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 08a871e8466..1b2ed5b7f42 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1075,6 +1075,7 @@ return array( 'OC\\Core\\Migrations\\Version24000Date20220425072957' => $baseDir . '/core/Migrations/Version24000Date20220425072957.php', 'OC\\Core\\Migrations\\Version25000Date20220515204012' => $baseDir . '/core/Migrations/Version25000Date20220515204012.php', 'OC\\Core\\Migrations\\Version25000Date20220602190540' => $baseDir . '/core/Migrations/Version25000Date20220602190540.php', + 'OC\\Core\\Migrations\\Version25000Date20220905140840' => $baseDir . '/core/Migrations/Version25000Date20220905140840.php', 'OC\\Core\\Migrations\\Version25000Date20221007010957' => $baseDir . '/core/Migrations/Version25000Date20221007010957.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index eec226c9a6e..a97a52c478b 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1108,6 +1108,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version24000Date20220425072957' => __DIR__ . '/../../..' . '/core/Migrations/Version24000Date20220425072957.php', 'OC\\Core\\Migrations\\Version25000Date20220515204012' => __DIR__ . '/../../..' . '/core/Migrations/Version25000Date20220515204012.php', 'OC\\Core\\Migrations\\Version25000Date20220602190540' => __DIR__ . '/../../..' . '/core/Migrations/Version25000Date20220602190540.php', + 'OC\\Core\\Migrations\\Version25000Date20220905140840' => __DIR__ . '/../../..' . '/core/Migrations/Version25000Date20220905140840.php', 'OC\\Core\\Migrations\\Version25000Date20221007010957' => __DIR__ . '/../../..' . '/core/Migrations/Version25000Date20221007010957.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php index d060fe14103..45335e17c31 100644 --- a/lib/private/Authentication/Token/PublicKeyToken.php +++ b/lib/private/Authentication/Token/PublicKeyToken.php @@ -45,6 +45,8 @@ use OCP\AppFramework\Db\Entity; * @method void setPublicKey(string $key) * @method void setVersion(int $version) * @method bool getPasswordInvalid() + * @method string getPasswordHash() + * @method setPasswordHash(string $hash) */ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken { public const VERSION = 2; @@ -58,6 +60,9 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken { /** @var string encrypted user password */ protected $password; + /** @var string hashed user password */ + protected $passwordHash; + /** @var string token name (e.g. browser/OS) */ protected $name; @@ -98,6 +103,7 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken { $this->addType('uid', 'string'); $this->addType('loginName', 'string'); $this->addType('password', 'string'); + $this->addType('passwordHash', 'string'); $this->addType('name', 'string'); $this->addType('token', 'string'); $this->addType('type', 'int'); diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php index c7e29568383..0aa60c9e8cd 100644 --- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php +++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php @@ -41,6 +41,7 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\IDBConnection; use OCP\Security\ICrypto; +use OCP\Security\IHasher; use Psr\Log\LoggerInterface; class PublicKeyTokenProvider implements IProvider { @@ -66,12 +67,15 @@ class PublicKeyTokenProvider implements IProvider { /** @var CappedMemoryCache */ private $cache; + private IHasher $hasher; + public function __construct(PublicKeyTokenMapper $mapper, ICrypto $crypto, IConfig $config, IDBConnection $db, LoggerInterface $logger, - ITimeFactory $time) { + ITimeFactory $time, + IHasher $hasher) { $this->mapper = $mapper; $this->crypto = $crypto; $this->config = $config; @@ -80,6 +84,7 @@ class PublicKeyTokenProvider implements IProvider { $this->time = $time; $this->cache = new CappedMemoryCache(); + $this->hasher = $hasher; } /** @@ -286,10 +291,15 @@ class PublicKeyTokenProvider implements IProvider { foreach ($tokens as $t) { $publicKey = $t->getPublicKey(); $t->setPassword($this->encryptPassword($password, $publicKey)); + $t->setPasswordHash($this->hashPassword($password)); $this->updateToken($t); } } + private function hashPassword(string $password): string { + return $this->hasher->hash(sha1($password) . $password); + } + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { $this->cache->clear(); @@ -401,6 +411,7 @@ class PublicKeyTokenProvider implements IProvider { throw new \RuntimeException('Trying to save a password with more than 469 characters is not supported. If you want to use big passwords, disable the auth.storeCryptedPassword option in config.php'); } $dbToken->setPassword($this->encryptPassword($password, $publicKey)); + $dbToken->setPasswordHash($this->hashPassword($password)); } $dbToken->setName($name); @@ -435,11 +446,12 @@ class PublicKeyTokenProvider implements IProvider { // Update the password for all tokens $tokens = $this->mapper->getTokenByUser($uid); + $passwordHash = $this->hashPassword($password); foreach ($tokens as $t) { $publicKey = $t->getPublicKey(); - $encryptedPassword = $this->encryptPassword($password, $publicKey); - if ($t->getPassword() !== $encryptedPassword) { - $t->setPassword($encryptedPassword); + if ($t->getPasswordHash() === null || $this->hasher->verify(sha1($password) . $password, $t->getPasswordHash())) { + $t->setPassword($this->encryptPassword($password, $publicKey)); + $t->setPasswordHash($passwordHash); $t->setPasswordInvalid(false); $this->updateToken($t); } diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php index d6e8dba31be..ca7618dfd6d 100644 --- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php +++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php @@ -64,6 +64,7 @@ class PublicKeyTokenProviderTest extends TestCase { parent::setUp(); $this->mapper = $this->createMock(PublicKeyTokenMapper::class); + $this->hasher = \OC::$server->getHasher(); $this->crypto = \OC::$server->getCrypto(); $this->config = $this->createMock(IConfig::class); $this->config->method('getSystemValue') @@ -87,6 +88,7 @@ class PublicKeyTokenProviderTest extends TestCase { $this->db, $this->logger, $this->timeFactory, + $this->hasher, ); } From bd6ac2841d37a078ade635ae5ebb7f878c6b1d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 3 Jan 2023 19:01:36 +0100 Subject: [PATCH 2/2] chore(version): Bump dev version to trigger database migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index 882a1997d4e..17f9bb2ed1f 100644 --- a/version.php +++ b/version.php @@ -30,7 +30,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = [26, 0, 0, 1]; +$OC_Version = [26, 0, 0, 2]; // The human readable string $OC_VersionString = '26.0.0 dev';