summaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php84
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php4
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Manager.php4
-rw-r--r--lib/private/Security/Crypto.php17
-rw-r--r--lib/private/Security/Hasher.php9
-rw-r--r--lib/private/Security/VerificationToken/VerificationToken.php9
6 files changed, 91 insertions, 36 deletions
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 0f1767e845b..c7e29568383 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -34,14 +34,18 @@ use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\TokenPasswordExpiredException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\WipeTokenException;
+use OCP\AppFramework\Db\TTransactional;
use OCP\Cache\CappedMemoryCache;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
+use OCP\IDBConnection;
use OCP\Security\ICrypto;
use Psr\Log\LoggerInterface;
class PublicKeyTokenProvider implements IProvider {
+ use TTransactional;
+
/** @var PublicKeyTokenMapper */
private $mapper;
@@ -51,6 +55,8 @@ class PublicKeyTokenProvider implements IProvider {
/** @var IConfig */
private $config;
+ private IDBConnection $db;
+
/** @var LoggerInterface */
private $logger;
@@ -63,11 +69,13 @@ class PublicKeyTokenProvider implements IProvider {
public function __construct(PublicKeyTokenMapper $mapper,
ICrypto $crypto,
IConfig $config,
+ IDBConnection $db,
LoggerInterface $logger,
ITimeFactory $time) {
$this->mapper = $mapper;
$this->crypto = $crypto;
$this->config = $config;
+ $this->db = $db;
$this->logger = $logger;
$this->time = $time;
@@ -111,8 +119,14 @@ class PublicKeyTokenProvider implements IProvider {
$token = $this->mapper->getToken($this->hashToken($tokenId));
$this->cache[$token->getToken()] = $token;
} catch (DoesNotExistException $ex) {
- $this->cache[$tokenHash] = $ex;
- throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
+ try {
+ $token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId));
+ $this->cache[$token->getToken()] = $token;
+ $this->rotate($token, $tokenId, $tokenId);
+ } catch (DoesNotExistException $ex2) {
+ $this->cache[$tokenHash] = $ex2;
+ throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
+ }
}
}
@@ -158,37 +172,39 @@ class PublicKeyTokenProvider implements IProvider {
public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
$this->cache->clear();
- $token = $this->getToken($oldSessionId);
-
- if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
- }
-
- $password = null;
- if (!is_null($token->getPassword())) {
- $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
- $password = $this->decryptPassword($token->getPassword(), $privateKey);
- }
-
- $newToken = $this->generateToken(
- $sessionId,
- $token->getUID(),
- $token->getLoginName(),
- $password,
- $token->getName(),
- IToken::TEMPORARY_TOKEN,
- $token->getRemember()
- );
+ return $this->atomic(function () use ($oldSessionId, $sessionId) {
+ $token = $this->getToken($oldSessionId);
- $this->mapper->delete($token);
+ if (!($token instanceof PublicKeyToken)) {
+ throw new InvalidTokenException("Invalid token type");
+ }
- return $newToken;
+ $password = null;
+ if (!is_null($token->getPassword())) {
+ $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
+ $password = $this->decryptPassword($token->getPassword(), $privateKey);
+ }
+ $newToken = $this->generateToken(
+ $sessionId,
+ $token->getUID(),
+ $token->getLoginName(),
+ $password,
+ $token->getName(),
+ IToken::TEMPORARY_TOKEN,
+ $token->getRemember()
+ );
+
+ $this->mapper->delete($token);
+
+ return $newToken;
+ }, $this->db);
}
public function invalidateToken(string $token) {
$this->cache->clear();
$this->mapper->invalidate($this->hashToken($token));
+ $this->mapper->invalidate($this->hashTokenWithEmptySecret($token));
}
public function invalidateTokenById(string $uid, int $id) {
@@ -305,9 +321,14 @@ class PublicKeyTokenProvider implements IProvider {
try {
return $this->crypto->decrypt($cipherText, $token . $secret);
} catch (\Exception $ex) {
- // Delete the invalid token
- $this->invalidateToken($token);
- throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex);
+ // Retry with empty secret as a fallback for instances where the secret might not have been set by accident
+ try {
+ return $this->crypto->decrypt($cipherText, $token);
+ } catch (\Exception $ex2) {
+ // Delete the invalid token
+ $this->invalidateToken($token);
+ throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex2);
+ }
}
}
@@ -331,6 +352,13 @@ class PublicKeyTokenProvider implements IProvider {
}
/**
+ * @deprecated Fallback for instances where the secret might not have been set by accident
+ */
+ private function hashTokenWithEmptySecret(string $token): string {
+ return hash('sha512', $token);
+ }
+
+ /**
* @throws \RuntimeException when OpenSSL reports a problem
*/
private function newToken(string $token,
diff --git a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
index e9aa15e11b6..19d80218562 100644
--- a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
+++ b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
@@ -47,7 +47,7 @@ class ProviderUserAssignmentDao {
/**
* Get all assigned provider IDs for the given user ID
*
- * @return string[] where the array key is the provider ID (string) and the
+ * @return array<string, bool> where the array key is the provider ID (string) and the
* value is the enabled state (bool)
*/
public function getState(string $uid): array {
@@ -59,7 +59,7 @@ class ProviderUserAssignmentDao {
$result = $query->execute();
$providers = [];
foreach ($result->fetchAll() as $row) {
- $providers[$row['provider_id']] = 1 === (int)$row['enabled'];
+ $providers[(string)$row['provider_id']] = 1 === (int)$row['enabled'];
}
$result->closeCursor();
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index 66e7c090e42..37a9f03d073 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -170,10 +170,10 @@ class Manager {
*
* @todo remove in Nextcloud 17 as by then all providers should have been updated
*
- * @param string[] $providerStates
+ * @param array<string, bool> $providerStates
* @param IProvider[] $providers
* @param IUser $user
- * @return string[] the updated $providerStates variable
+ * @return array<string, bool> the updated $providerStates variable
*/
private function fixMissingProviderStates(array $providerStates,
array $providers, IUser $user): array {
diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php
index e9ef4417925..aeeafcc271c 100644
--- a/lib/private/Security/Crypto.php
+++ b/lib/private/Security/Crypto.php
@@ -122,9 +122,22 @@ class Crypto implements ICrypto {
* @throws Exception If the decryption failed
*/
public function decrypt(string $authenticatedCiphertext, string $password = ''): string {
- if ($password === '') {
- $password = $this->config->getSystemValue('secret');
+ $secret = $this->config->getSystemValue('secret');
+ try {
+ if ($password === '') {
+ return $this->decryptWithoutSecret($authenticatedCiphertext, $secret);
+ }
+ return $this->decryptWithoutSecret($authenticatedCiphertext, $password);
+ } catch (Exception $e) {
+ if ($password === '') {
+ // Retry with empty secret as a fallback for instances where the secret might not have been set by accident
+ return $this->decryptWithoutSecret($authenticatedCiphertext, '');
+ }
+ throw $e;
}
+ }
+
+ private function decryptWithoutSecret(string $authenticatedCiphertext, string $password = ''): string {
$hmacKey = $encryptionKey = $password;
$parts = explode('|', $authenticatedCiphertext);
diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php
index 5b3fc2b47a9..4731ba96bd3 100644
--- a/lib/private/Security/Hasher.php
+++ b/lib/private/Security/Hasher.php
@@ -137,6 +137,15 @@ class Hasher implements IHasher {
return true;
}
+ // Verify whether it matches a legacy PHPass or SHA1 string
+ // Retry with empty passwordsalt for cases where it was not set
+ $hashLength = \strlen($hash);
+ if (($hashLength === 60 && password_verify($message, $hash)) ||
+ ($hashLength === 40 && hash_equals($hash, sha1($message)))) {
+ $newHash = $this->hash($message);
+ return true;
+ }
+
return false;
}
diff --git a/lib/private/Security/VerificationToken/VerificationToken.php b/lib/private/Security/VerificationToken/VerificationToken.php
index c85e0e7b5a1..2d3f902b622 100644
--- a/lib/private/Security/VerificationToken/VerificationToken.php
+++ b/lib/private/Security/VerificationToken/VerificationToken.php
@@ -84,10 +84,15 @@ class VerificationToken implements IVerificationToken {
try {
$decryptedToken = $this->crypto->decrypt($encryptedToken, $passwordPrefix.$this->config->getSystemValue('secret'));
} catch (\Exception $e) {
- $this->throwInvalidTokenException(InvalidTokenException::TOKEN_DECRYPTION_ERROR);
+ // Retry with empty secret as a fallback for instances where the secret might not have been set by accident
+ try {
+ $decryptedToken = $this->crypto->decrypt($encryptedToken, $passwordPrefix);
+ } catch (\Exception $e2) {
+ $this->throwInvalidTokenException(InvalidTokenException::TOKEN_DECRYPTION_ERROR);
+ }
}
- $splitToken = explode(':', $decryptedToken ?? '');
+ $splitToken = explode(':', $decryptedToken);
if (count($splitToken) !== 2) {
$this->throwInvalidTokenException(InvalidTokenException::TOKEN_INVALID_FORMAT);
}