--- /dev/null
+<?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;
+ }
+}
'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',
'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',
* @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;
/** @var string encrypted user password */
protected $password;
+ /** @var string hashed user password */
+ protected $passwordHash;
+
/** @var string token name (e.g. browser/OS) */
protected $name;
$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');
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
+use OCP\Security\IHasher;
use Psr\Log\LoggerInterface;
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;
$this->time = $time;
$this->cache = new CappedMemoryCache();
+ $this->hasher = $hasher;
}
/**
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();
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);
// 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);
}
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')
$this->db,
$this->logger,
$this->timeFactory,
+ $this->hasher,
);
}