mirror of
https://github.com/nextcloud/server.git
synced 2026-06-15 19:49:38 -04:00
Merge pull request #35605 from nextcloud/backport/stable25/31499
[stable25] Add fallback routines for empty secret cases
This commit is contained in:
commit
9eb99ecbab
5 changed files with 70 additions and 12 deletions
|
|
@ -119,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +204,7 @@ class PublicKeyTokenProvider implements IProvider {
|
|||
$this->cache->clear();
|
||||
|
||||
$this->mapper->invalidate($this->hashToken($token));
|
||||
$this->mapper->invalidate($this->hashTokenWithEmptySecret($token));
|
||||
}
|
||||
|
||||
public function invalidateTokenById(string $uid, int $id) {
|
||||
|
|
@ -314,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -339,6 +351,13 @@ class PublicKeyTokenProvider implements IProvider {
|
|||
return hash('sha512', $token . $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,9 +310,12 @@ class PublicKeyTokenProviderTest extends TestCase {
|
|||
}
|
||||
|
||||
public function testInvalidateToken() {
|
||||
$this->mapper->expects($this->once())
|
||||
$this->mapper->expects($this->at(0))
|
||||
->method('invalidate')
|
||||
->with(hash('sha512', 'token7'.'1f4h9s'));
|
||||
$this->mapper->expects($this->at(1))
|
||||
->method('invalidate')
|
||||
->with(hash('sha512', 'token7'));
|
||||
|
||||
$this->tokenProvider->invalidateToken('token7');
|
||||
}
|
||||
|
|
@ -443,13 +446,22 @@ class PublicKeyTokenProviderTest extends TestCase {
|
|||
public function testGetInvalidToken() {
|
||||
$this->expectException(InvalidTokenException::class);
|
||||
|
||||
$this->mapper->method('getToken')
|
||||
$this->mapper->expects($this->at(0))
|
||||
->method('getToken')
|
||||
->with(
|
||||
$this->callback(function (string $token) {
|
||||
$this->callback(function (string $token): bool {
|
||||
return hash('sha512', 'unhashedToken'.'1f4h9s') === $token;
|
||||
})
|
||||
)->willThrowException(new DoesNotExistException('nope'));
|
||||
|
||||
$this->mapper->expects($this->at(1))
|
||||
->method('getToken')
|
||||
->with(
|
||||
$this->callback(function (string $token): bool {
|
||||
return hash('sha512', 'unhashedToken') === $token;
|
||||
})
|
||||
)->willThrowException(new DoesNotExistException('nope'));
|
||||
|
||||
$this->tokenProvider->getToken('unhashedToken');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue