PublickKeyTokenProvider: Fix password update routine with password hash

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Marcel Klehr 2022-12-01 18:06:58 +01:00 committed by Julius Härtl
parent b37a4950e4
commit adfe367106
No known key found for this signature in database
GPG Key ID: 4C614C6ED2CDE6DF
6 changed files with 83 additions and 4 deletions

View File

@ -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;
}
}

View 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',

View 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',

View 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');

View 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);
}

View 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,
);
}