]> source.dussan.org Git - nextcloud-server.git/commitdiff
PublickKeyTokenProvider: Fix password update routine with password hash
authorMarcel Klehr <mklehr@gmx.net>
Thu, 1 Dec 2022 17:06:58 +0000 (18:06 +0100)
committerJulius Härtl <jus@bitgrid.net>
Wed, 4 Jan 2023 07:30:53 +0000 (08:30 +0100)
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
core/Migrations/Version25000Date20220905140840.php [new file with mode: 0644]
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Authentication/Token/PublicKeyToken.php
lib/private/Authentication/Token/PublicKeyTokenProvider.php
tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php

diff --git a/core/Migrations/Version25000Date20220905140840.php b/core/Migrations/Version25000Date20220905140840.php
new file mode 100644 (file)
index 0000000..6cda613
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2022 Marcel Klehr <mklehr@gmx.net>
+ *
+ * @author Marcel Klehr <mklehr@gmx.net>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+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;
+       }
+}
index 08a871e84669f2e4e39dcdcc6c9bc5ce5d407ac3..1b2ed5b7f429902b5ed20bf446002ce0d666bee6 100644 (file)
@@ -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',
index eec226c9a6e2f8c83758aaac4b473ccb5d71043f..a97a52c478ba2ce5dfe8d7dc0f1b5b7c1f2e3053 100644 (file)
@@ -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',
index d060fe141032b0c7ad5296b64f6725c1985285fb..45335e17c3161ee961de6cb9a9e01ca1fdf2e281 100644 (file)
@@ -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');
index c7e295683831df962a079b14ad9d0ca51e000f20..0aa60c9e8cdc7b7b94f7be4a25a2f473d35e9c6e 100644 (file)
@@ -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);
                        }
index d6e8dba31bef438a4fe498f42dd47f4866befe49..ca7618dfd6d7dcf7664666c2d6d39c5daad02993 100644 (file)
@@ -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,
                );
        }