diff --git a/apps/encryption/lib/Controller/RecoveryController.php b/apps/encryption/lib/Controller/RecoveryController.php index f24490dfc5797..869be63696c34 100644 --- a/apps/encryption/lib/Controller/RecoveryController.php +++ b/apps/encryption/lib/Controller/RecoveryController.php @@ -29,7 +29,13 @@ public function __construct( parent::__construct($appName, $request); } - public function adminRecovery(string $recoveryPassword, string $confirmPassword, bool $adminEnableRecovery): DataResponse { + public function adminRecovery( + #[\SensitiveParameter] + string $recoveryPassword, + #[\SensitiveParameter] + string $confirmPassword, + bool $adminEnableRecovery, + ): DataResponse { // Check if both passwords are the same if (empty($recoveryPassword)) { $errorMessage = $this->l->t('Missing recovery key password'); @@ -70,7 +76,14 @@ public function adminRecovery(string $recoveryPassword, string $confirmPassword, } } - public function changeRecoveryPassword(string $newPassword, string $oldPassword, string $confirmPassword): DataResponse { + public function changeRecoveryPassword( + #[\SensitiveParameter] + string $newPassword, + #[\SensitiveParameter] + string $oldPassword, + #[\SensitiveParameter] + string $confirmPassword, + ): DataResponse { //check if both passwords are the same if (empty($oldPassword)) { $errorMessage = $this->l->t('Please provide the old recovery password'); diff --git a/apps/encryption/lib/Controller/SettingsController.php b/apps/encryption/lib/Controller/SettingsController.php index 3ccd851f2c159..5cd5078bb3355 100644 --- a/apps/encryption/lib/Controller/SettingsController.php +++ b/apps/encryption/lib/Controller/SettingsController.php @@ -53,14 +53,14 @@ public function __construct( parent::__construct($appName, $request); } - /** - * @param string $oldPassword - * @param string $newPassword - * @return DataResponse - */ #[NoAdminRequired] #[UseSession] - public function updatePrivateKeyPassword($oldPassword, $newPassword) { + public function updatePrivateKeyPassword( + #[\SensitiveParameter] + string $oldPassword, + #[\SensitiveParameter] + string $newPassword, + ): DataResponse { $result = false; $uid = $this->userSession->getUser()->getUID(); $errorMessage = $this->l->t('Could not update the private key password.'); diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php index 9972be1edace8..abcb0c81e0944 100644 --- a/apps/encryption/lib/Crypto/Crypt.php +++ b/apps/encryption/lib/Crypto/Crypt.php @@ -141,7 +141,14 @@ private function getOpenSSLConfig(): array { /** * @throws EncryptionFailedException */ - public function symmetricEncryptFileContent(string $plainContent, string $passPhrase, int $version, string $position): string|false { + public function symmetricEncryptFileContent( + #[\SensitiveParameter] + string $plainContent, + #[\SensitiveParameter] + string $passPhrase, + int $version, + string $position, + ): string|false { if (!$plainContent) { $this->logger->error('Encryption Library, symmetrical encryption failed no content given', ['app' => 'encryption']); @@ -192,7 +199,15 @@ public function generateHeader($keyFormat = self::DEFAULT_KEY_FORMAT) { /** * @throws EncryptionFailedException */ - private function encrypt(string $plainContent, string $iv, string $passPhrase = '', string $cipher = self::DEFAULT_CIPHER): string { + private function encrypt( + #[\SensitiveParameter] + string $plainContent, + #[\SensitiveParameter] + string $iv, + #[\SensitiveParameter] + string $passPhrase = '', + string $cipher = self::DEFAULT_CIPHER, + ): string { $options = $this->useLegacyBase64Encoding ? 0 : OPENSSL_RAW_DATA; $encryptedContent = openssl_encrypt($plainContent, $cipher, @@ -302,7 +317,14 @@ private function addPadding(string $data): string { * * @param string $uid only used for user keys */ - protected function generatePasswordHash(string $password, string $cipher, string $uid = '', int $iterations = 600000): string { + protected function generatePasswordHash( + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + string $cipher, + string $uid = '', + int $iterations = 600000, + ): string { $instanceId = $this->config->getSystemValue('instanceid'); $instanceSecret = $this->config->getSystemValue('secret'); $salt = hash('sha256', $uid . $instanceId . $instanceSecret, true); @@ -321,12 +343,15 @@ protected function generatePasswordHash(string $password, string $cipher, string /** * encrypt private key * - * @param string $privateKey - * @param string $password * @param string $uid for regular users, empty for system keys - * @return false|string */ - public function encryptPrivateKey($privateKey, $password, $uid = '') { + public function encryptPrivateKey( + #[\SensitiveParameter] + string $privateKey, + #[\SensitiveParameter] + string $password, + string $uid = '', + ): string|false { $cipher = $this->getCipher(); $hash = $this->generatePasswordHash($password, $cipher, $uid); $encryptedKey = $this->symmetricEncryptFileContent( @@ -340,11 +365,15 @@ public function encryptPrivateKey($privateKey, $password, $uid = '') { } /** - * @param string $privateKey - * @param string $password * @param string $uid for regular users, empty for system keys */ - public function decryptPrivateKey($privateKey, $password = '', $uid = '') : string|false { + public function decryptPrivateKey( + #[\SensitiveParameter] + string $privateKey, + #[\SensitiveParameter] + string $password = '', + string $uid = '', + ) : string|false { $header = $this->parseHeader($privateKey); if (isset($header['cipher'])) { @@ -392,11 +421,11 @@ public function decryptPrivateKey($privateKey, $password = '', $uid = '') : stri /** * check if it is a valid private key - * - * @param string $plainKey - * @return bool */ - protected function isValidPrivateKey($plainKey) { + protected function isValidPrivateKey( + #[\SensitiveParameter] + string $plainKey, + ): bool { $res = openssl_get_privatekey($plainKey); if (is_object($res) && get_class($res) === 'OpenSSLAsymmetricKey') { $sslInfo = openssl_pkey_get_details($res); @@ -411,7 +440,17 @@ protected function isValidPrivateKey($plainKey) { /** * @throws DecryptionFailedException */ - public function symmetricDecryptFileContent(string $keyFileContents, string $passPhrase, string $cipher = self::DEFAULT_CIPHER, int $version = 0, int|string $position = 0, bool $binaryEncoding = false): string { + public function symmetricDecryptFileContent( + #[\SensitiveParameter] + string $keyFileContents, + #[\SensitiveParameter] + string $passPhrase, + #[\SensitiveParameter] + string $cipher = self::DEFAULT_CIPHER, + int $version = 0, + int|string $position = 0, + bool $binaryEncoding = false, + ): string { if ($keyFileContents === '') { return ''; } @@ -440,7 +479,14 @@ public function symmetricDecryptFileContent(string $keyFileContents, string $pas * * @throws GenericEncryptionException */ - private function checkSignature(string $data, string $passPhrase, string $expectedSignature): void { + private function checkSignature( + #[\SensitiveParameter] + string $data, + #[\SensitiveParameter] + string $passPhrase, + #[\SensitiveParameter] + string $expectedSignature, + ): void { $enforceSignature = !$this->config->getSystemValueBool('encryption_skip_signature_check', false); $signature = $this->createSignature($data, $passPhrase); @@ -458,7 +504,12 @@ private function checkSignature(string $data, string $passPhrase, string $expect /** * create signature */ - private function createSignature(string $data, string $passPhrase): string { + private function createSignature( + #[\SensitiveParameter] + string $data, + #[\SensitiveParameter] + string $passPhrase, + ): string { $passPhrase = hash('sha512', $passPhrase . 'a', true); return hash_hmac('sha256', $data, $passPhrase); } @@ -529,7 +580,17 @@ private function hasSignature(string $catFile, string $cipher): bool { /** * @throws DecryptionFailedException */ - private function decrypt(string $encryptedContent, string $iv, string $passPhrase = '', string $cipher = self::DEFAULT_CIPHER, bool $binaryEncoding = false): string { + private function decrypt( + #[\SensitiveParameter] + string $encryptedContent, + #[\SensitiveParameter] + string $iv, + #[\SensitiveParameter] + string $passPhrase = '', + #[\SensitiveParameter] + string $cipher = self::DEFAULT_CIPHER, + bool $binaryEncoding = false, + ): string { $options = $binaryEncoding === true ? OPENSSL_RAW_DATA : 0; $plainContent = openssl_decrypt($encryptedContent, $cipher, @@ -594,7 +655,12 @@ public function generateFileKey() { * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $privateKey * @throws MultiKeyDecryptException */ - public function multiKeyDecrypt(string $shareKey, $privateKey): string { + public function multiKeyDecrypt( + #[\SensitiveParameter] + string $shareKey, + #[\SensitiveParameter] + $privateKey, + ): string { $plainContent = ''; // decrypt the intermediate key with RSA @@ -626,7 +692,12 @@ public function multiKeyDecryptLegacy(string $encKeyFile, string $shareKey, $pri * @param array $keyFiles * @throws MultiKeyEncryptException */ - public function multiKeyEncrypt(string $plainContent, array $keyFiles): array { + public function multiKeyEncrypt( + #[\SensitiveParameter] + string $plainContent, + #[\SensitiveParameter] + array $keyFiles, + ): array { if (empty($plainContent)) { throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content'); } diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index fc1d55ab7b480..d979472ddd8d0 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -262,7 +262,11 @@ public function end($path, $position = '0') { * @return string encrypted data */ #[\Override] - public function encrypt($data, $position = 0) { + public function encrypt( + #[\SensitiveParameter] + $data, + $position = 0, + ): string { // If extra data is left over from the last round, make sure it // is integrated into the next block if ($this->writeCache) { @@ -321,7 +325,11 @@ public function encrypt($data, $position = 0) { * @throws DecryptionFailedException */ #[\Override] - public function decrypt($data, $position = 0) { + public function decrypt( + #[\SensitiveParameter] + $data, + $position = 0, + ): string { if (empty($this->fileKey)) { $msg = 'Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.'; $hint = $this->l->t('Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.'); diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 1f65dfe0936e2..0490535742404 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -171,11 +171,10 @@ public function getRecoveryKeyId() { return $this->recoveryKeyId; } - /** - * @param string $password - * @return bool - */ - public function checkRecoveryPassword($password) { + public function checkRecoveryPassword( + #[\SensitiveParameter] + string $password, + ): bool { $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.' . $this->privateKeyId, Encryption::ID); $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); @@ -185,13 +184,13 @@ public function checkRecoveryPassword($password) { return false; } - /** - * @param string $uid - * @param string $password - * @param array $keyPair - * @return bool - */ - public function storeKeyPair($uid, $password, $keyPair) { + public function storeKeyPair( + string $uid, + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + array $keyPair, + ): bool { // Save Public Key $this->setPublicKey($uid, $keyPair['publicKey']); @@ -206,12 +205,12 @@ public function storeKeyPair($uid, $password, $keyPair) { return false; } - /** - * @param string $password - * @param array $keyPair - * @return bool - */ - public function setRecoveryKey($password, $keyPair) { + public function setRecoveryKey( + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + array $keyPair, + ): bool { // Save Public Key $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId() . '.' . $this->publicKeyId, @@ -237,12 +236,11 @@ public function setPublicKey($userId, $key) { return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID); } - /** - * @param $userId - * @param string $key - * @return bool - */ - public function setPrivateKey($userId, $key) { + public function setPrivateKey( + string $userId, + #[\SensitiveParameter] + string $key, + ): bool { return $this->keyStorage->setUserKey($userId, $this->privateKeyId, $key, @@ -251,22 +249,27 @@ public function setPrivateKey($userId, $key) { /** * write file key to key storage - * - * @param string $path - * @param string $key - * @return boolean */ - public function setFileKey($path, $key) { + public function setFileKey( + #[\SensitiveParameter] + string $path, + #[\SensitiveParameter] + string $key, + ): bool { return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID); } /** * set all file keys (the file key and the corresponding share keys) * - * @param string $path - * @param array $keys - */ - public function setAllFileKeys($path, $keys) { + * @param array{data:string,keys:array} $keys + */ + public function setAllFileKeys( + #[\SensitiveParameter] + string $path, + #[\SensitiveParameter] + array $keys, + ): void { $this->setFileKey($path, $keys['data']); foreach ($keys['keys'] as $uid => $keyFile) { $this->setShareKey($path, $uid, $keyFile); @@ -332,11 +335,9 @@ public function init(string $uid, ?string $passPhrase) { } /** - * @param $userId - * @return string * @throws PrivateKeyMissingException */ - public function getPrivateKey($userId) { + public function getPrivateKey(string $userId): string { $privateKey = $this->keyStorage->getUserKey($userId, $this->privateKeyId, Encryption::ID); @@ -598,7 +599,7 @@ public function getPublicKeys(array $userIds) { * @param string $keyId * @return string returns openssl key */ - public function getSystemPrivateKey($keyId) { + public function getSystemPrivateKey($keyId): string { return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID); } diff --git a/apps/encryption/lib/Listeners/UserEventsListener.php b/apps/encryption/lib/Listeners/UserEventsListener.php index 5a0320e8228eb..c9e0a9ea2ae9c 100644 --- a/apps/encryption/lib/Listeners/UserEventsListener.php +++ b/apps/encryption/lib/Listeners/UserEventsListener.php @@ -45,7 +45,10 @@ public function __construct( } #[\Override] - public function handle(Event $event): void { + public function handle( + #[\SensitiveParameter] + Event $event, + ): void { if ($event instanceof UserCreatedEvent) { $this->onUserCreated($event->getUid(), $event->getPassword()); } elseif ($event instanceof UserDeletedEvent) { @@ -68,7 +71,11 @@ public function handle(Event $event): void { /** * Startup encryption backend upon user login */ - private function onUserLogin(IUser $user, ?string $password): void { + private function onUserLogin( + IUser $user, + #[\SensitiveParameter] + ?string $password, + ): void { // Do not try to setup filesystem if the current request does not have permissions to access it if (!$this->lockdownManager->canAccessFilesystem()) { return; @@ -100,7 +107,11 @@ private function onUserLogout(): void { * * This method should never be called for users using client side encryption */ - protected function onUserCreated(string $userId, string $password): void { + protected function onUserCreated( + string $userId, + #[\SensitiveParameter] + string $password, + ): void { $this->userSetup->setupUser($userId, $password); } @@ -116,7 +127,13 @@ protected function onUserDeleted(string $userId): void { /** * If the password can't be changed within Nextcloud, than update the key password in advance. */ - public function onBeforePasswordUpdated(IUser $user, string $password, ?string $recoveryPassword = null): void { + public function onBeforePasswordUpdated( + IUser $user, + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + ?string $recoveryPassword = null, + ): void { if (!$user->canChangePassword()) { $this->passphraseService->setPassphraseForUser($user->getUID(), $password, $recoveryPassword); } @@ -125,7 +142,13 @@ public function onBeforePasswordUpdated(IUser $user, string $password, ?string $ /** * Change a user's encryption passphrase */ - public function onPasswordUpdated(string $userId, string $password, ?string $recoveryPassword): void { + public function onPasswordUpdated( + string $userId, + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + ?string $recoveryPassword, + ): void { $this->passphraseService->setPassphraseForUser($userId, $password, $recoveryPassword); } @@ -139,7 +162,11 @@ public function onBeforePasswordReset(string $userId): void { /** * Create new encryption keys on password reset and backup the old one */ - public function onPasswordReset(string $userId, string $password): void { + public function onPasswordReset( + string $userId, + #[\SensitiveParameter] + string $password, + ): void { $this->keyManager->backupUserKeys('passwordReset', $userId); $this->keyManager->deleteUserKeys($userId); $this->userSetup->setupUser($userId, $password); diff --git a/apps/encryption/lib/Services/PassphraseService.php b/apps/encryption/lib/Services/PassphraseService.php index 592a85572db4f..15c3ef415d201 100644 --- a/apps/encryption/lib/Services/PassphraseService.php +++ b/apps/encryption/lib/Services/PassphraseService.php @@ -47,7 +47,13 @@ public function setProcessingReset(string $uid, bool $processing = true): void { /** * Change a user's encryption passphrase */ - public function setPassphraseForUser(string $userId, string $password, ?string $recoveryPassword = null): bool { + public function setPassphraseForUser( + string $userId, + #[\SensitiveParameter] + string $password, + #[\SensitiveParameter] + ?string $recoveryPassword = null, + ): bool { // if we are in the process to resetting a user password, we have nothing // to do here if (isset($this->passwordResetUsers[$userId])) { diff --git a/apps/encryption/lib/Session.php b/apps/encryption/lib/Session.php index 437450b7e7c7d..69d86d96bebb1 100644 --- a/apps/encryption/lib/Session.php +++ b/apps/encryption/lib/Session.php @@ -87,14 +87,21 @@ public function isPrivateKeySet(): bool { * * @note this should only be set on login */ - public function setPrivateKey(string $key): void { + public function setPrivateKey( + #[\SensitiveParameter] + string $key, + ): void { $this->session->set('privateKey', $key); } /** * store data needed for the decrypt all operation in the session */ - public function prepareDecryptAll(string $user, string $key): void { + public function prepareDecryptAll( + string $user, + #[\SensitiveParameter] + string $key, + ): void { $this->session->set('decryptAll', true); $this->session->set('decryptAllKey', $key); $this->session->set('decryptAllUid', $user); diff --git a/apps/encryption/lib/Users/Setup.php b/apps/encryption/lib/Users/Setup.php index a20f13b92e1fd..b187ee4e692ce 100644 --- a/apps/encryption/lib/Users/Setup.php +++ b/apps/encryption/lib/Users/Setup.php @@ -24,12 +24,11 @@ public function __construct( $this->cache = $cacheFactory->createLocal('encryption-setup'); } - /** - * @param string $uid user id - * @param string $password user password - * @return bool - */ - public function setupUser($uid, $password) { + public function setupUser( + string $uid, + #[\SensitiveParameter] + string $password, + ): bool { if (!$this->keyManager->userHasKeys($uid)) { $keyPair = $this->crypt->createKeyPair(); return is_array($keyPair) ? $this->keyManager->storeKeyPair($uid, $password, $keyPair) : false; diff --git a/apps/encryption/tests/KeyManagerTest.php b/apps/encryption/tests/KeyManagerTest.php index 4ab727a56f1b4..a510f136133c4 100644 --- a/apps/encryption/tests/KeyManagerTest.php +++ b/apps/encryption/tests/KeyManagerTest.php @@ -562,6 +562,9 @@ public function testValidateMasterKey($masterKey): void { ->willReturn(['publicKey' => 'public', 'privateKey' => 'private']); $this->keyStorageMock->expects($this->once())->method('setSystemUserKey') ->with('systemKeyId.publicKey', 'public', Encryption::ID); + $this->keyStorageMock->expects($this->atLeastOnce())->method('getSystemUserKey') + ->with('systemKeyId.privateKey', Encryption::ID) + ->willReturn(''); $this->cryptMock->expects($this->once())->method('encryptPrivateKey') ->with('private', 'masterKeyPassword', 'systemKeyId') ->willReturn('EncryptedKey'); @@ -572,6 +575,9 @@ public function testValidateMasterKey($masterKey): void { } else { $this->cryptMock->expects($this->never())->method('createKeyPair'); $this->keyStorageMock->expects($this->never())->method('setSystemUserKey'); + $this->keyStorageMock->expects($this->atLeastOnce())->method('getSystemUserKey') + ->with('systemKeyId.privateKey', Encryption::ID) + ->willReturn('systemUserKey'); $this->cryptMock->expects($this->never())->method('encryptPrivateKey'); $instance->expects($this->never())->method('setSystemPrivateKey'); } diff --git a/core/Controller/SetupController.php b/core/Controller/SetupController.php index f2d0231bac9c0..ca56ef41c67b4 100644 --- a/core/Controller/SetupController.php +++ b/core/Controller/SetupController.php @@ -32,7 +32,10 @@ public function __construct( $this->autoConfigFile = \OC::$configDir . 'autoconfig.php'; } - public function run(array $post): void { + public function run( + #[\SensitiveParameter] + array $post, + ): void { // Check for autosetup: $post = $this->loadAutoConfig($post); $opts = $this->setupHelper->getSystemInfo(); @@ -71,7 +74,10 @@ private function displaySetupForbidden(): void { $this->templateManager->printGuestPage('', 'installation_forbidden'); } - public function display(array $post): void { + public function display( + #[\SensitiveParameter] + array $post, + ): void { $defaults = [ 'adminlogin' => '', 'adminpass' => '', @@ -133,7 +139,10 @@ private function finishSetup(): void { /** * @psalm-taint-escape file we trust file path given in POST for setup */ - public function loadAutoConfig(array $post): array { + public function loadAutoConfig( + #[\SensitiveParameter] + array $post, + ): array { if (file_exists($this->autoConfigFile)) { $this->logger->info('Autoconfig file found, setting up Nextcloud…'); $AUTOCONFIG = []; diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php index 97ef67d49f66d..c3dc4d93fba15 100644 --- a/lib/private/Http/Client/Client.php +++ b/lib/private/Http/Client/Client.php @@ -210,7 +210,12 @@ protected function preventLocalAddress(string $uri, array $options): void { * @throws \Exception If the request could not get completed */ #[\Override] - public function get(string $uri, array $options = []): IResponse { + public function get( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('get', $uri, $this->buildRequestOptions($options)); $isStream = isset($options['stream']) && $options['stream']; @@ -242,7 +247,12 @@ public function get(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function head(string $uri, array $options = []): IResponse { + public function head( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('head', $uri, $this->buildRequestOptions($options)); return new Response($response); @@ -278,7 +288,12 @@ public function head(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function post(string $uri, array $options = []): IResponse { + public function post( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); if (isset($options['body']) && is_array($options['body'])) { @@ -320,7 +335,12 @@ public function post(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function put(string $uri, array $options = []): IResponse { + public function put( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('put', $uri, $this->buildRequestOptions($options)); return new Response($response); @@ -356,7 +376,12 @@ public function put(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function patch(string $uri, array $options = []): IResponse { + public function patch( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('patch', $uri, $this->buildRequestOptions($options)); return new Response($response); @@ -392,7 +417,12 @@ public function patch(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function delete(string $uri, array $options = []): IResponse { + public function delete( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('delete', $uri, $this->buildRequestOptions($options)); return new Response($response); @@ -428,7 +458,12 @@ public function delete(string $uri, array $options = []): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function options(string $uri, array $options = []): IResponse { + public function options( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request('options', $uri, $this->buildRequestOptions($options)); return new Response($response); @@ -482,7 +517,13 @@ public function getResponseFromThrowable(\Throwable $e): IResponse { * @throws \Exception If the request could not get completed */ #[\Override] - public function request(string $method, string $uri, array $options = []): IResponse { + public function request( + string $method, + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IResponse { $this->preventLocalAddress($uri, $options); $response = $this->client->request($method, $uri, $this->buildRequestOptions($options)); $isStream = isset($options['stream']) && $options['stream']; @@ -531,7 +572,12 @@ protected function wrapGuzzlePromise(PromiseInterface $promise): IPromise { * @return IPromise */ #[\Override] - public function getAsync(string $uri, array $options = []): IPromise { + public function getAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); $response = $this->client->requestAsync('get', $uri, $this->buildRequestOptions($options)); return $this->wrapGuzzlePromise($response); @@ -561,7 +607,12 @@ public function getAsync(string $uri, array $options = []): IPromise { * @return IPromise */ #[\Override] - public function headAsync(string $uri, array $options = []): IPromise { + public function headAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); $response = $this->client->requestAsync('head', $uri, $this->buildRequestOptions($options)); return $this->wrapGuzzlePromise($response); @@ -596,7 +647,12 @@ public function headAsync(string $uri, array $options = []): IPromise { * @return IPromise */ #[\Override] - public function postAsync(string $uri, array $options = []): IPromise { + public function postAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); if (isset($options['body']) && is_array($options['body'])) { @@ -636,7 +692,12 @@ public function postAsync(string $uri, array $options = []): IPromise { * @return IPromise */ #[\Override] - public function putAsync(string $uri, array $options = []): IPromise { + public function putAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); $response = $this->client->requestAsync('put', $uri, $this->buildRequestOptions($options)); return $this->wrapGuzzlePromise($response); @@ -671,7 +732,12 @@ public function putAsync(string $uri, array $options = []): IPromise { * @return IPromise */ #[\Override] - public function deleteAsync(string $uri, array $options = []): IPromise { + public function deleteAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); $response = $this->client->requestAsync('delete', $uri, $this->buildRequestOptions($options)); return $this->wrapGuzzlePromise($response); @@ -706,7 +772,12 @@ public function deleteAsync(string $uri, array $options = []): IPromise { * @return IPromise */ #[\Override] - public function optionsAsync(string $uri, array $options = []): IPromise { + public function optionsAsync( + #[\SensitiveParameter] + string $uri, + #[\SensitiveParameter] + array $options = [], + ): IPromise { $this->preventLocalAddress($uri, $options); $response = $this->client->requestAsync('options', $uri, $this->buildRequestOptions($options)); return $this->wrapGuzzlePromise($response); diff --git a/lib/private/Log/ExceptionSerializer.php b/lib/private/Log/ExceptionSerializer.php index c39f3e005f065..9697c3cedf0db 100644 --- a/lib/private/Log/ExceptionSerializer.php +++ b/lib/private/Log/ExceptionSerializer.php @@ -7,18 +7,8 @@ namespace OC\Log; -use OC\Core\Controller\SetupController; -use OC\Http\Client\Client; -use OC\Security\IdentityProof\Key; -use OC\Setup; use OC\SystemConfig; -use OCA\Encryption\Controller\RecoveryController; -use OCA\Encryption\Controller\SettingsController; -use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\Crypto\Encryption; -use OCA\Encryption\KeyManager; -use OCA\Encryption\Listeners\UserEventsListener; -use OCA\Encryption\Services\PassphraseService; use OCA\Encryption\Session; use OCP\HintException; @@ -98,92 +88,7 @@ public function __construct( ) { } - protected array $methodsWithSensitiveParametersByClass = [ - SetupController::class => [ - 'run', - 'display', - 'loadAutoConfig', - ], - Setup::class => [ - 'install' - ], - Key::class => [ - '__construct' - ], - Client::class => [ - 'request', - 'delete', - 'deleteAsync', - 'get', - 'getAsync', - 'head', - 'headAsync', - 'options', - 'optionsAsync', - 'patch', - 'post', - 'postAsync', - 'put', - 'putAsync', - ], - \Redis::class => [ - 'auth' - ], - \RedisCluster::class => [ - '__construct' - ], - Crypt::class => [ - 'symmetricEncryptFileContent', - 'encrypt', - 'generatePasswordHash', - 'encryptPrivateKey', - 'decryptPrivateKey', - 'isValidPrivateKey', - 'symmetricDecryptFileContent', - 'checkSignature', - 'createSignature', - 'decrypt', - 'multiKeyDecrypt', - 'multiKeyEncrypt', - ], - RecoveryController::class => [ - 'adminRecovery', - 'changeRecoveryPassword' - ], - SettingsController::class => [ - 'updatePrivateKeyPassword', - ], - Encryption::class => [ - 'encrypt', - 'decrypt', - ], - KeyManager::class => [ - 'checkRecoveryPassword', - 'storeKeyPair', - 'setRecoveryKey', - 'setPrivateKey', - 'setFileKey', - 'setAllFileKeys', - ], - Session::class => [ - 'setPrivateKey', - 'prepareDecryptAll', - ], - \OCA\Encryption\Users\Setup::class => [ - 'setupUser', - ], - UserEventsListener::class => [ - 'handle', - 'onUserCreated', - 'onUserLogin', - 'onBeforePasswordUpdated', - 'onPasswordUpdated', - 'onPasswordReset', - ], - PassphraseService::class => [ - 'setPassphraseForUser', - ], - ]; + protected array $methodsWithSensitiveParametersByClass = []; private function editTrace(array &$sensitiveValues, array $traceLine): array { if (isset($traceLine['args'])) { @@ -193,6 +98,18 @@ private function editTrace(array &$sensitiveValues, array $traceLine): array { return $traceLine; } + private function processSensitiveParameterValues(array &$sensitiveValues, array $traceLine): array { + if (isset($traceLine['args'])) { + foreach ($traceLine['args'] as &$arg) { + if ($arg instanceof \SensitiveParameterValue) { + $sensitiveValues[] = $arg->getValue(); + $arg = self::SENSITIVE_VALUE_PLACEHOLDER; + } + } + } + return $traceLine; + } + private function filterTrace(array $trace) { $sensitiveValues = []; $trace = array_map(function (array $traceLine) use (&$sensitiveValues) { @@ -208,7 +125,7 @@ private function filterTrace(array $trace) { return $this->editTrace($sensitiveValues, $traceLine); } } - return $traceLine; + return $this->processSensitiveParameterValues($sensitiveValues, $traceLine); }, $trace); return array_map(function (array $traceLine) use ($sensitiveValues) { if (isset($traceLine['args'])) { @@ -218,7 +135,7 @@ private function filterTrace(array $trace) { }, $trace); } - private function removeValuesFromArgs($args, $values): array { + private function removeValuesFromArgs(array $args, array $values): array { $workArgs = []; foreach ($args as $key => $arg) { if (in_array($arg, $values, true)) { @@ -231,18 +148,22 @@ private function removeValuesFromArgs($args, $values): array { return $workArgs; } - private function encodeTrace($trace) { + private function encodeTrace(array $trace): array { $trace = array_map(function (array $line) { if (isset($line['args'])) { - $line['args'] = array_map([$this, 'encodeArg'], $line['args']); + $line['args'] = array_map($this->encodeArg(...), $line['args']); } return $line; }, $trace); return $this->filterTrace($trace); } - private function encodeArg($arg, $nestingLevel = 5) { + private function encodeArg(mixed $arg, int $nestingLevel = 5): mixed { if (is_object($arg)) { + if ($arg instanceof \SensitiveParameterValue) { + // Keep SensitiveParameterValue as-is, it will be handled later + return $arg; + } if ($nestingLevel === 0) { return [ '__class__' => get_class($arg), diff --git a/lib/private/Security/IdentityProof/Key.php b/lib/private/Security/IdentityProof/Key.php index c1b435f730add..44a85a8452cdf 100644 --- a/lib/private/Security/IdentityProof/Key.php +++ b/lib/private/Security/IdentityProof/Key.php @@ -11,7 +11,9 @@ class Key { public function __construct( + #[\SensitiveParameter] private string $publicKey, + #[\SensitiveParameter] private string $privateKey, ) { } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 60403125eff62..2f25b6bea6a8b 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -318,7 +318,11 @@ public function isHtaccessWorking(string $dataDir): bool { /** * @return array errors */ - public function install(array $options, ?IOutput $output = null): array { + public function install( + #[\SensitiveParameter] + array $options, + ?IOutput $output = null, + ): array { $l = $this->l10n; $error = []; diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index 6609c4747051e..598bbb5fdae73 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -381,6 +381,7 @@ public function registerUserMigrator(string $migratorClass): void; * @param string[] $methods * @return void * @since 25.0.0 + * @deprecated 36.0.0 use \SensitiveParameter attribute instead */ public function registerSensitiveMethods(string $class, array $methods): void; diff --git a/tests/lib/Log/ExceptionSerializerTest.php b/tests/lib/Log/ExceptionSerializerTest.php index e543c34f1be18..fd2406ff2ebb1 100644 --- a/tests/lib/Log/ExceptionSerializerTest.php +++ b/tests/lib/Log/ExceptionSerializerTest.php @@ -33,7 +33,19 @@ private function bind(array &$myValues): void { } private function customMagicAuthThing(string $login, string $parole): void { - throw new \Exception('expected custom auth exception'); + $this->throwsException($parole); + } + + private function customSensitiveParameter( + string $login, + #[\SensitiveParameter] + string $password, + ): void { + $this->throwsException($password); + } + + private function throwsException(string $maybeSecret): void { + throw new \Exception('expected exception'); } /** @@ -60,9 +72,24 @@ public function testSerializerWithRegisteredMethods(): void { $this->customMagicAuthThing('u57474', 'Secret'); } catch (\Exception $e) { $serializedData = $this->serializer->serializeException($e); - $this->assertSame('customMagicAuthThing', $serializedData['Trace'][0]['function']); + $this->assertSame('throwsException', $serializedData['Trace'][0]['function']); + $this->assertSame(ExceptionSerializer::SENSITIVE_VALUE_PLACEHOLDER, $serializedData['Trace'][0]['args'][0]); + $this->assertSame('customMagicAuthThing', $serializedData['Trace'][1]['function']); + $this->assertSame(ExceptionSerializer::SENSITIVE_VALUE_PLACEHOLDER, $serializedData['Trace'][1]['args'][0]); + $this->assertFalse(isset($serializedData['Trace'][1]['args'][1])); + } + } + + public function testSerializerWithSensitiveParameter(): void { + try { + $this->customSensitiveParameter('login', 'Secret'); + } catch (\Exception $e) { + $serializedData = $this->serializer->serializeException($e); + $this->assertSame('throwsException', $serializedData['Trace'][0]['function']); $this->assertSame(ExceptionSerializer::SENSITIVE_VALUE_PLACEHOLDER, $serializedData['Trace'][0]['args'][0]); - $this->assertFalse(isset($serializedData['Trace'][0]['args'][1])); + $this->assertSame('customSensitiveParameter', $serializedData['Trace'][1]['function']); + $this->assertSame('login', $serializedData['Trace'][1]['args'][0]); + $this->assertSame(ExceptionSerializer::SENSITIVE_VALUE_PLACEHOLDER, $serializedData['Trace'][1]['args'][1]); } } }