/** * @NoAdminRequired * @UseSession * * @param string $oldPassword * @param string $newPassword * @return DataResponse */ public function updatePrivateKeyPassword($oldPassword, $newPassword) { $result = false; $uid = $this->userSession->getUser()->getUID(); $errorMessage = $this->l->t('Could not update the private key password.'); //check if password is correct $passwordCorrect = $this->userManager->checkPassword($uid, $newPassword); if ($passwordCorrect !== false) { $encryptedKey = $this->keyManager->getPrivateKey($uid); $decryptedKey = $this->crypt->decryptPrivateKey($encryptedKey, $oldPassword); if ($decryptedKey) { $encryptedKey = $this->crypt->symmetricEncryptFileContent($decryptedKey, $newPassword); $header = $this->crypt->generateHeader(); if ($encryptedKey) { $this->keyManager->setPrivateKey($uid, $header . $encryptedKey); $this->session->setPrivateKey($decryptedKey); $result = true; } } else { $errorMessage = $this->l->t('The old password was not correct, please try again.'); } } else { $errorMessage = $this->l->t('The current log-in password was not correct, please try again.'); } if ($result === true) { $this->session->setStatus(Session::INIT_SUCCESSFUL); return new DataResponse(['message' => (string) $this->l->t('Private key password successfully updated.')]); } else { return new DataResponse(['message' => (string) $errorMessage], Http::STATUS_BAD_REQUEST); } }
/** * @dataProvider dataTestGetFileKey * * @param $uid * @param $isMasterKeyEnabled * @param $privateKey * @param $expected */ public function testGetFileKey($uid, $isMasterKeyEnabled, $privateKey, $expected) { $path = '/foo.txt'; if ($isMasterKeyEnabled) { $expectedUid = 'masterKeyId'; } else { $expectedUid = $uid; } $this->invokePrivate($this->instance, 'masterKeyId', ['masterKeyId']); $this->keyStorageMock->expects($this->at(0))->method('getFileKey')->with($path, 'fileKey', 'OC_DEFAULT_MODULE')->willReturn(true); $this->keyStorageMock->expects($this->at(1))->method('getFileKey')->with($path, $expectedUid . '.shareKey', 'OC_DEFAULT_MODULE')->willReturn(true); if (is_null($uid)) { $this->keyStorageMock->expects($this->once())->method('getSystemUserKey')->willReturn(true); $this->cryptMock->expects($this->once())->method('decryptPrivateKey')->willReturn($privateKey); } else { $this->keyStorageMock->expects($this->never())->method('getSystemUserKey'); $this->utilMock->expects($this->once())->method('isMasterKeyEnabled')->willReturn($isMasterKeyEnabled); $this->sessionMock->expects($this->once())->method('getPrivateKey')->willReturn($privateKey); } if ($privateKey) { $this->cryptMock->expects($this->once())->method('multiKeyDecrypt')->willReturn(true); } else { $this->cryptMock->expects($this->never())->method('multiKeyDecrypt'); } $this->assertSame($expected, $this->instance->getFileKey($path, $uid)); }
/** * start receiving chunks from a file. This is the place where you can * perform some initial step before starting encrypting/decrypting the * chunks * * @param string $path to the file * @param string $user who read/write the file * @param string $mode php stream open mode * @param array $header contains the header data read from the file * @param array $accessList who has access to the file contains the key 'users' and 'public' * * @return array $header contain data as key-value pairs which should be * written to the header, in case of a write operation * or if no additional data is needed return a empty array */ public function begin($path, $user, $mode, array $header, array $accessList) { $this->path = $this->getPathToRealFile($path); $this->accessList = $accessList; $this->user = $user; $this->isWriteOperation = false; $this->writeCache = ''; if ($this->session->decryptAllModeActivated()) { $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path); $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid()); $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey, $shareKey, $this->session->getDecryptAllKey()); } else { $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user); } if ($mode === 'w' || $mode === 'w+' || $mode === 'wb' || $mode === 'wb+') { $this->isWriteOperation = true; if (empty($this->fileKey)) { $this->fileKey = $this->crypt->generateFileKey(); } } if (isset($header['cipher'])) { $this->cipher = $header['cipher']; } elseif ($this->isWriteOperation) { $this->cipher = $this->crypt->getCipher(); } else { // if we read a file without a header we fall-back to the legacy cipher // which was used in <=oC6 $this->cipher = $this->crypt->getLegacyCipher(); } return array('cipher' => $this->cipher); }
/** * */ public function testClearWillRemoveValues() { $this->instance->setPrivateKey('privateKey'); $this->instance->setStatus('initStatus'); $this->instance->prepareDecryptAll('user', 'key'); $this->assertNotEmpty(self::$tempStorage); $this->instance->clear(); $this->assertEmpty(self::$tempStorage); }
/** * @NoAdminRequired * @return DataResponse */ public function getStatus() { $status = 'error'; $message = 'no valid init status'; switch ($this->session->getStatus()) { case Session::RUN_MIGRATION: $status = 'interactionNeeded'; $message = (string) $this->l->t('You need to migrate your encryption keys from the old encryption (ownCloud <= 8.0) to the new one. Please run \'occ encryption:migrate\' or contact your administrator'); break; case Session::INIT_EXECUTED: $status = 'interactionNeeded'; $message = (string) $this->l->t('Invalid private key for Encryption App. Please update your private key password in your personal settings to recover access to your encrypted files.'); break; case Session::NOT_INITIALIZED: $status = 'interactionNeeded'; $message = (string) $this->l->t('Encryption App is enabled but your keys are not initialized, please log-out and log-in again'); break; case Session::INIT_SUCCESSFUL: $status = 'success'; $message = (string) $this->l->t('Encryption App is enabled and ready'); } return new DataResponse(['status' => $status, 'data' => ['message' => $message]]); }
/** * @param $path raw path relative to data/ * @param $mode * @param $options * @param $opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path) { // assume that the file already exist before we decide it finally in getKey() $this->newFile = false; if (!isset($this->rootView)) { $this->rootView = new \OC_FilesystemView('/'); } $this->session = new \OCA\Encryption\Session($this->rootView); $this->privateKey = $this->session->getPrivateKey(); // rawPath is relative to the data directory $this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); $this->userId = Helper::getUser($this->rawPath); $util = new Util($this->rootView, $this->userId); // get the key ID which we want to use, can be the users key or the // public share key $this->keyId = $util->getKeyId(); // Strip identifier text from path, this gives us the path relative to data/<user>/files $this->relPath = Helper::stripUserFilesPath($this->rawPath); // if raw path doesn't point to a real file, check if it is a version or a file in the trash bin if ($this->relPath === false) { $this->relPath = Helper::getPathToRealFile($this->rawPath); } if ($this->relPath === false) { \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '" expecting a path to "files", "files_versions" or "cache"', \OCP\Util::ERROR); return false; } // Disable fileproxies so we can get the file size and open the source file without recursive encryption $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; if ($mode === 'w' or $mode === 'w+' or $mode === 'wb' or $mode === 'wb+') { // We're writing a new file so start write counter with 0 bytes $this->size = 0; $this->unencryptedSize = 0; } else { if ($this->privateKey === false) { // if private key is not valid redirect user to a error page \OCA\Encryption\Helper::redirectToErrorPage($this->session); } $this->size = $this->rootView->filesize($this->rawPath, $mode); } $this->handle = $this->rootView->fopen($this->rawPath, $mode); \OC_FileProxy::$enabled = $proxyStatus; if (!is_resource($this->handle)) { \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); } else { $this->meta = stream_get_meta_data($this->handle); } return is_resource($this->handle); }
/** * Change a user's encryption passphrase * * @param array $params keys: uid, password * @return boolean|null */ public function setPassphrase($params) { // Get existing decrypted private key $privateKey = $this->session->getPrivateKey(); $user = $this->user->getUser(); // current logged in user changes his own password if ($user && $params['uid'] === $user->getUID() && $privateKey) { // Encrypt private key with new user pwd as passphrase $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']); // Save private key if ($encryptedPrivateKey) { $this->keyManager->setPrivateKey($this->user->getUser()->getUID(), $this->crypt->generateHeader() . $encryptedPrivateKey); } else { $this->logger->error('Encryption could not update users encryption password'); } // NOTE: Session does not need to be updated as the // private key has not changed, only the passphrase // used to decrypt it has changed } else { // admin changed the password for a different user, create new keys and re-encrypt file keys $user = $params['uid']; $this->initMountPoints($user); $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null; // we generate new keys if... // ...we have a recovery password and the user enabled the recovery key // ...encryption was activated for the first time (no keys exists) // ...the user doesn't have any files if ($this->recovery->isRecoveryEnabledForUser($user) && $recoveryPassword || !$this->keyManager->userHasKeys($user) || !$this->util->userHasFiles($user)) { // backup old keys //$this->backupAllKeys('recovery'); $newUserPassword = $params['password']; $keyPair = $this->crypt->createKeyPair(); // Save public key $this->keyManager->setPublicKey($user, $keyPair['publicKey']); // Encrypt private key with new password $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $user); if ($encryptedKey) { $this->keyManager->setPrivateKey($user, $this->crypt->generateHeader() . $encryptedKey); if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files $this->recovery->recoverUsersFiles($recoveryPassword, $user); } } else { $this->logger->error('Encryption Could not update users encryption password'); } } } }
/** * test begin() if decryptAll mode was activated */ public function testBeginDecryptAll() { $path = '/user/files/foo.txt'; $recoveryKeyId = 'recoveryKeyId'; $recoveryShareKey = 'recoveryShareKey'; $decryptAllKey = 'decryptAllKey'; $fileKey = 'fileKey'; $this->sessionMock->expects($this->once())->method('decryptAllModeActivated')->willReturn(true); $this->sessionMock->expects($this->once())->method('getDecryptAllUid')->willReturn($recoveryKeyId); $this->sessionMock->expects($this->once())->method('getDecryptAllKey')->willReturn($decryptAllKey); $this->keyManagerMock->expects($this->once())->method('getEncryptedFileKey')->willReturn('encryptedFileKey'); $this->keyManagerMock->expects($this->once())->method('getShareKey')->with($path, $recoveryKeyId)->willReturn($recoveryShareKey); $this->cryptMock->expects($this->once())->method('multiKeyDecrypt')->with('encryptedFileKey', $recoveryShareKey, $decryptAllKey)->willReturn($fileKey); $this->keyManagerMock->expects($this->never())->method('getFileKey'); $this->instance->begin($path, 'user', 'r', [], []); $this->assertSame($fileKey, $this->invokePrivate($this->instance, 'fileKey')); }
/** * @param $path * @param $uid * @return string */ public function getFileKey($path, $uid) { $encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID); if (is_null($uid)) { $uid = $this->getPublicShareKeyId(); $shareKey = $this->getShareKey($path, $uid); $privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID); $privateKey = $this->crypt->decryptPrivateKey($privateKey); } else { $shareKey = $this->getShareKey($path, $uid); $privateKey = $this->session->getPrivateKey(); } if ($encryptedFileKey && $shareKey && $privateKey) { return $this->crypt->multiKeyDecrypt($encryptedFileKey, $shareKey, $privateKey); } return ''; }
/** * test updatePrivateKeyPassword() with the correct old and new password */ public function testUpdatePrivateKeyPassword() { $oldPassword = '******'; $newPassword = '******'; $this->ocSessionMock->expects($this->once())->method('get')->with('loginname')->willReturn('testUser'); $this->userManagerMock->expects($this->at(0))->method('checkPassword')->with('testUserUid', 'new')->willReturn(false); $this->userManagerMock->expects($this->at(1))->method('checkPassword')->with('testUser', 'new')->willReturn(true); $this->cryptMock->expects($this->once())->method('decryptPrivateKey')->willReturn('decryptedKey'); $this->cryptMock->expects($this->once())->method('encryptPrivateKey')->willReturn('encryptedKey'); $this->cryptMock->expects($this->once())->method('generateHeader')->willReturn('header.'); // methods which must be called after successful changing the key password $this->keyManagerMock->expects($this->once())->method('setPrivateKey')->with($this->equalTo('testUserUid'), $this->equalTo('header.encryptedKey')); $this->sessionMock->expects($this->once())->method('setPrivateKey')->with($this->equalTo('decryptedKey')); $this->sessionMock->expects($this->once())->method('setStatus')->with($this->equalTo(Session::INIT_SUCCESSFUL)); $result = $this->controller->updatePrivateKeyPassword($oldPassword, $newPassword); $data = $result->getData(); $this->assertSame(Http::STATUS_OK, $result->getStatus()); $this->assertSame('Private key password successfully updated.', $data['message']); }
/** * start receiving chunks from a file. This is the place where you can * perform some initial step before starting encrypting/decrypting the * chunks * * @param string $path to the file * @param string $user who read/write the file * @param string $mode php stream open mode * @param array $header contains the header data read from the file * @param array $accessList who has access to the file contains the key 'users' and 'public' * * @return array $header contain data as key-value pairs which should be * written to the header, in case of a write operation * or if no additional data is needed return a empty array */ public function begin($path, $user, $mode, array $header, array $accessList) { $this->path = $this->getPathToRealFile($path); $this->accessList = $accessList; $this->user = $user; $this->isWriteOperation = false; $this->writeCache = ''; if ($this->session->decryptAllModeActivated()) { $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path); $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid()); $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey, $shareKey, $this->session->getDecryptAllKey()); } else { $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user); } // always use the version from the original file, also part files // need to have a correct version number if they get moved over to the // final location $this->version = (int) $this->keyManager->getVersion($this->stripPartFileExtension($path), new View()); if ($mode === 'w' || $mode === 'w+' || $mode === 'wb' || $mode === 'wb+') { $this->isWriteOperation = true; if (empty($this->fileKey)) { $this->fileKey = $this->crypt->generateFileKey(); } } else { // if we read a part file we need to increase the version by 1 // because the version number was also increased by writing // the part file if (Scanner::isPartialFile($path)) { $this->version = $this->version + 1; } } if ($this->isWriteOperation) { $this->cipher = $this->crypt->getCipher(); } elseif (isset($header['cipher'])) { $this->cipher = $header['cipher']; } else { // if we read a file without a header we fall-back to the legacy cipher // which was used in <=oC6 $this->cipher = $this->crypt->getLegacyCipher(); } return array('cipher' => $this->cipher, 'signed' => 'true'); }
/** * @NoAdminRequired * @UseSession * * @param string $oldPassword * @param string $newPassword * @return DataResponse */ public function updatePrivateKeyPassword($oldPassword, $newPassword) { $result = false; $uid = $this->userSession->getUser()->getUID(); $errorMessage = $this->l->t('Could not update the private key password.'); //check if password is correct $passwordCorrect = $this->userManager->checkPassword($uid, $newPassword); if ($passwordCorrect === false) { // if check with uid fails we need to check the password with the login name // e.g. in the ldap case. For local user we need to check the password with // the uid because in this case the login name is case insensitive $loginName = $this->ocSession->get('loginname'); $passwordCorrect = $this->userManager->checkPassword($loginName, $newPassword); } if ($passwordCorrect !== false) { $encryptedKey = $this->keyManager->getPrivateKey($uid); $decryptedKey = $this->crypt->decryptPrivateKey($encryptedKey, $oldPassword, $uid); if ($decryptedKey) { $encryptedKey = $this->crypt->encryptPrivateKey($decryptedKey, $newPassword, $uid); $header = $this->crypt->generateHeader(); if ($encryptedKey) { $this->keyManager->setPrivateKey($uid, $header . $encryptedKey); $this->session->setPrivateKey($decryptedKey); $result = true; } } else { $errorMessage = $this->l->t('The old password was not correct, please try again.'); } } else { $errorMessage = $this->l->t('The current log-in password was not correct, please try again.'); } if ($result === true) { $this->session->setStatus(Session::INIT_SUCCESSFUL); return new DataResponse(['message' => (string) $this->l->t('Private key password successfully updated.')]); } else { return new DataResponse(['message' => (string) $errorMessage], Http::STATUS_BAD_REQUEST); } }
/** * @param $path * @param $mode * @param $options * @param $opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path) { if (!isset($this->rootView)) { $this->rootView = new \OC_FilesystemView('/'); } $this->session = new \OCA\Encryption\Session($this->rootView); $this->privateKey = $this->session->getPrivateKey($this->userId); $util = new Util($this->rootView, \OCP\USER::getUser()); $this->userId = $util->getUserId(); // Strip identifier text from path, this gives us the path relative to data/<user>/files $this->relPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); // rawPath is relative to the data directory $this->rawPath = $util->getUserFilesDir() . $this->relPath; // Disable fileproxies so we can get the file size and open the source file without recursive encryption $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; if ($mode === 'w' or $mode === 'w+' or $mode === 'wb' or $mode === 'wb+') { // We're writing a new file so start write counter with 0 bytes $this->size = 0; $this->unencryptedSize = 0; } else { if ($this->privateKey === false) { // if private key is not valid redirect user to a error page \OCA\Encryption\Helper::redirectToErrorPage(); } $this->size = $this->rootView->filesize($this->rawPath, $mode); } $this->handle = $this->rootView->fopen($this->rawPath, $mode); \OC_FileProxy::$enabled = $proxyStatus; if (!is_resource($this->handle)) { \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); } else { $this->meta = stream_get_meta_data($this->handle); } return is_resource($this->handle); }
protected function updateSession($user, $privateKey) { $this->session->prepareDecryptAll($user, $privateKey); }
/** * */ public function testClearWillRemoveValues() { $this->instance->clear(); $this->assertEmpty(self::$tempStorage); }
/** * Encrypt keyfile to multiple users * @param Session $session * @param array $users list of users which should be able to access the file * @param string $filePath path of the file to be shared * @return bool */ public function setSharedFileKeyfiles(Session $session, array $users, $filePath) { // Make sure users are capable of sharing $filteredUids = $this->filterShareReadyUsers($users); // If we're attempting to share to unready users if (!empty($filteredUids['unready'])) { \OCP\Util::writeLog('Encryption library', 'Sharing to these user(s) failed as they are unready for encryption:"' . print_r($filteredUids['unready'], 1), \OCP\Util::WARN); return false; } // Get public keys for each user, ready for generating sharekeys $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); // Note proxy status then disable it $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // Get the current users's private key for decrypting existing keyfile $privateKey = $session->getPrivateKey(); try { // Decrypt keyfile $plainKeyfile = $this->decryptKeyfile($filePath, $privateKey); // Re-enc keyfile to (additional) sharekeys $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); } catch (Exceptions\EncryptionException $e) { $msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage(); \OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL); return false; } catch (\Exception $e) { $msg = 'set shareFileKeyFailed (unknown error): ' . $e->getMessage(); \OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL); return false; } // Save the recrypted key to it's owner's keyfiles directory // Save new sharekeys to all necessary user directory if (!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data']) || !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])) { \OCP\Util::writeLog('Encryption library', 'Keyfiles could not be saved for users sharing ' . $filePath, \OCP\Util::ERROR); return false; } // Return proxy to original status \OC_FileProxy::$enabled = $proxyStatus; return true; }