diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/encryption/appinfo/application.php | 15 | ||||
-rw-r--r-- | apps/encryption/lib/crypto/decryptall.php | 143 | ||||
-rw-r--r-- | apps/encryption/lib/crypto/encryption.php | 36 | ||||
-rw-r--r-- | apps/encryption/lib/session.php | 60 | ||||
-rw-r--r-- | apps/encryption/tests/lib/SessionTest.php | 55 | ||||
-rw-r--r-- | apps/encryption/tests/lib/crypto/decryptalltest.php | 125 | ||||
-rw-r--r-- | apps/encryption/tests/lib/crypto/encryptionTest.php | 78 |
7 files changed, 510 insertions, 2 deletions
diff --git a/apps/encryption/appinfo/application.php b/apps/encryption/appinfo/application.php index 75107b2723c..515a408fa2c 100644 --- a/apps/encryption/appinfo/application.php +++ b/apps/encryption/appinfo/application.php @@ -30,6 +30,7 @@ use OCA\Encryption\Controller\RecoveryController; use OCA\Encryption\Controller\SettingsController; use OCA\Encryption\Controller\StatusController; use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\Crypto\DecryptAll; use OCA\Encryption\Crypto\EncryptAll; use OCA\Encryption\Crypto\Encryption; use OCA\Encryption\HookManager; @@ -113,7 +114,9 @@ class Application extends \OCP\AppFramework\App { $container->query('Crypt'), $container->query('KeyManager'), $container->query('Util'), + $container->query('Session'), $container->query('EncryptAll'), + $container->query('DecryptAll'), $container->getServer()->getLogger(), $container->getServer()->getL10N($container->getAppName()) ); @@ -242,6 +245,18 @@ class Application extends \OCP\AppFramework\App { } ); + $container->registerService('DecryptAll', + function (IAppContainer $c) { + return new DecryptAll( + $c->query('Util'), + $c->query('KeyManager'), + $c->query('Crypt'), + $c->query('Session'), + new QuestionHelper() + ); + } + ); + } public function registerSettings() { diff --git a/apps/encryption/lib/crypto/decryptall.php b/apps/encryption/lib/crypto/decryptall.php new file mode 100644 index 00000000000..599cd82aa4d --- /dev/null +++ b/apps/encryption/lib/crypto/decryptall.php @@ -0,0 +1,143 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OCA\Encryption\Crypto; + + +use OCA\Encryption\KeyManager; +use OCA\Encryption\Session; +use OCA\Encryption\Util; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +class DecryptAll { + + /** @var Util */ + protected $util; + + /** @var QuestionHelper */ + protected $questionHelper; + + /** @var Crypt */ + protected $crypt; + + /** @var KeyManager */ + protected $keyManager; + + /** @var Session */ + protected $session; + + /** + * @param Util $util + * @param KeyManager $keyManager + * @param Crypt $crypt + * @param Session $session + * @param QuestionHelper $questionHelper + */ + public function __construct( + Util $util, + KeyManager $keyManager, + Crypt $crypt, + Session $session, + QuestionHelper $questionHelper + ) { + $this->util = $util; + $this->keyManager = $keyManager; + $this->crypt = $crypt; + $this->session = $session; + $this->questionHelper = $questionHelper; + } + + /** + * prepare encryption module to decrypt all files + * + * @param InputInterface $input + * @param OutputInterface $output + * @param $user + * @return bool + */ + public function prepare(InputInterface $input, OutputInterface $output, $user) { + + $question = new Question('Please enter the recovery key password: '); + $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); + + if (!empty($user)) { + $questionUseLoginPassword = new ConfirmationQuestion( + 'Do you want to use the users login password to decrypt all files? (y/n) ', + false + ); + $useLoginPassword = $this->questionHelper->ask($input, $output, $questionUseLoginPassword); + if ($useLoginPassword) { + $question = new Question('Please enter the users login password: '); + } else if ($this->util->isRecoveryEnabledForUser($user) === false) { + $output->writeln('No recovery key available for user ' . $user); + return false; + } else { + $user = $recoveryKeyId; + } + } else { + $user = $recoveryKeyId; + } + + $question->setHidden(true); + $question->setHiddenFallback(false); + $password = $this->questionHelper->ask($input, $output, $question); + $privateKey = $this->getPrivateKey($user, $password); + if ($privateKey !== false) { + $this->updateSession($user, $privateKey); + return true; + } else { + $output->writeln('Could not decrypt private key, maybe you entered the wrong password?'); + } + + + return false; + } + + /** + * get the private key which will be used to decrypt all files + * + * @param string $user + * @param string $password + * @return bool|string + * @throws \OCA\Encryption\Exceptions\PrivateKeyMissingException + */ + protected function getPrivateKey($user, $password) { + $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); + if ($user === $recoveryKeyId) { + $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); + $privateKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); + } else { + $userKey = $this->keyManager->getPrivateKey($user); + $privateKey = $this->crypt->decryptPrivateKey($userKey, $password, $user); + } + + return $privateKey; + } + + protected function updateSession($user, $privateKey) { + $this->session->prepareDecryptAll($user, $privateKey); + } +} diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php index c62afac83c1..fde4a2c4a9c 100644 --- a/apps/encryption/lib/crypto/encryption.php +++ b/apps/encryption/lib/crypto/encryption.php @@ -30,6 +30,7 @@ namespace OCA\Encryption\Crypto; use OC\Encryption\Exceptions\DecryptionFailedException; use OCA\Encryption\Exceptions\PublicKeyMissingException; +use OCA\Encryption\Session; use OCA\Encryption\Util; use OCP\Encryption\IEncryptionModule; use OCA\Encryption\KeyManager; @@ -75,6 +76,9 @@ class Encryption implements IEncryptionModule { /** @var Util */ private $util; + /** @var Session */ + private $session; + /** @var ILogger */ private $logger; @@ -87,25 +91,34 @@ class Encryption implements IEncryptionModule { /** @var bool */ private $useMasterPassword; + /** @var DecryptAll */ + private $decryptAll; + /** * * @param Crypt $crypt * @param KeyManager $keyManager * @param Util $util + * @param Session $session * @param EncryptAll $encryptAll + * @param DecryptAll $decryptAll * @param ILogger $logger * @param IL10N $il10n */ public function __construct(Crypt $crypt, KeyManager $keyManager, Util $util, + Session $session, EncryptAll $encryptAll, + DecryptAll $decryptAll, ILogger $logger, IL10N $il10n) { $this->crypt = $crypt; $this->keyManager = $keyManager; $this->util = $util; + $this->session = $session; $this->encryptAll = $encryptAll; + $this->decryptAll = $decryptAll; $this->logger = $logger; $this->l = $il10n; $this->useMasterPassword = $util->isMasterKeyEnabled(); @@ -150,7 +163,15 @@ class Encryption implements IEncryptionModule { $this->isWriteOperation = false; $this->writeCache = ''; - $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user); + 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' @@ -427,6 +448,19 @@ class Encryption implements IEncryptionModule { } /** + * prepare module to perform decrypt all operation + * + * @param InputInterface $input + * @param OutputInterface $output + * @param string $user + * @return bool + */ + public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') { + return $this->decryptAll->prepare($input, $output, $user); + } + + + /** * @param string $path * @return string */ diff --git a/apps/encryption/lib/session.php b/apps/encryption/lib/session.php index c3759c3fc56..1d0c3711487 100644 --- a/apps/encryption/lib/session.php +++ b/apps/encryption/lib/session.php @@ -25,6 +25,7 @@ namespace OCA\Encryption; +use OCA\Encryption\Exceptions\PrivateKeyMissingException; use \OCP\ISession; class Session { @@ -106,6 +107,61 @@ class Session { $this->session->set('privateKey', $key); } + /** + * store data needed for the decrypt all operation in the session + * + * @param string $user + * @param string $key + */ + public function prepareDecryptAll($user, $key) { + $this->session->set('decryptAll', true); + $this->session->set('decryptAllKey', $key); + $this->session->set('decryptAllUid', $user); + } + + /** + * check if we are in decrypt all mode + * + * @return bool + */ + public function decryptAllModeActivated() { + $decryptAll = $this->session->get('decryptAll'); + return ($decryptAll === true); + } + + /** + * get uid used for decrypt all operation + * + * @return string + * @throws \Exception + */ + public function getDecryptAllUid() { + $uid = $this->session->get('decryptAllUid'); + if (is_null($uid) && $this->decryptAllModeActivated()) { + throw new \Exception('No uid found while in decrypt all mode'); + } elseif (is_null($uid)) { + throw new \Exception('Please activate decrypt all mode first'); + } + + return $uid; + } + + /** + * get private key for decrypt all operation + * + * @return string + * @throws PrivateKeyMissingException + */ + public function getDecryptAllKey() { + $privateKey = $this->session->get('decryptAllKey'); + if (is_null($privateKey) && $this->decryptAllModeActivated()) { + throw new PrivateKeyMissingException('No private key found while in decrypt all mode'); + } elseif (is_null($privateKey)) { + throw new PrivateKeyMissingException('Please activate decrypt all mode first'); + } + + return $privateKey; + } /** * remove keys from session @@ -114,7 +170,9 @@ class Session { $this->session->remove('publicSharePrivateKey'); $this->session->remove('privateKey'); $this->session->remove('encryptionInitialized'); - + $this->session->remove('decryptAll'); + $this->session->remove('decryptAllKey'); + $this->session->remove('decryptAllUid'); } } diff --git a/apps/encryption/tests/lib/SessionTest.php b/apps/encryption/tests/lib/SessionTest.php index e036c439939..0fa48666d70 100644 --- a/apps/encryption/tests/lib/SessionTest.php +++ b/apps/encryption/tests/lib/SessionTest.php @@ -56,6 +56,7 @@ class SessionTest extends TestCase { * @depends testSetAndGetPrivateKey */ public function testIsPrivateKeySet() { + $this->instance->setPrivateKey('dummyPrivateKey'); $this->assertTrue($this->instance->isPrivateKeySet()); unset(self::$tempStorage['privateKey']); @@ -65,6 +66,51 @@ class SessionTest extends TestCase { self::$tempStorage['privateKey'] = 'dummyPrivateKey'; } + public function testDecryptAllModeActivated() { + $this->instance->prepareDecryptAll('user1', 'usersKey'); + $this->assertTrue($this->instance->decryptAllModeActivated()); + $this->assertSame('user1', $this->instance->getDecryptAllUid()); + $this->assertSame('usersKey', $this->instance->getDecryptAllKey()); + } + + public function testDecryptAllModeDeactivated() { + $this->assertFalse($this->instance->decryptAllModeActivated()); + } + + /** + * @expectedException \Exception + * @expectExceptionMessage 'Please activate decrypt all mode first' + */ + public function testGetDecryptAllUidException() { + $this->instance->getDecryptAllUid(); + } + + /** + * @expectedException \Exception + * @expectExceptionMessage 'No uid found while in decrypt all mode' + */ + public function testGetDecryptAllUidException2() { + $this->instance->prepareDecryptAll(null, 'key'); + $this->instance->getDecryptAllUid(); + } + + /** + * @expectedException \OCA\Encryption\Exceptions\PrivateKeyMissingException + * @expectExceptionMessage 'Please activate decrypt all mode first' + */ + public function testGetDecryptAllKeyException() { + $this->instance->getDecryptAllKey(); + } + + /** + * @expectedException \OCA\Encryption\Exceptions\PrivateKeyMissingException + * @expectExceptionMessage 'No key found while in decrypt all mode' + */ + public function testGetDecryptAllKeyException2() { + $this->instance->prepareDecryptAll('user', null); + $this->instance->getDecryptAllKey(); + } + /** * */ @@ -112,6 +158,10 @@ class SessionTest extends TestCase { * */ 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); } @@ -138,4 +188,9 @@ class SessionTest extends TestCase { $this->instance = new Session($this->sessionMock); } + + protected function tearDown() { + self::$tempStorage = []; + parent::tearDown(); + } } diff --git a/apps/encryption/tests/lib/crypto/decryptalltest.php b/apps/encryption/tests/lib/crypto/decryptalltest.php new file mode 100644 index 00000000000..d6a52fe97c0 --- /dev/null +++ b/apps/encryption/tests/lib/crypto/decryptalltest.php @@ -0,0 +1,125 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + + +namespace OCA\Encryption\Tests\lib\Crypto; + + +use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\Crypto\DecryptAll; +use OCA\Encryption\KeyManager; +use OCA\Encryption\Session; +use OCA\Encryption\Util; +use Symfony\Component\Console\Helper\QuestionHelper; +use Test\TestCase; + +class DecryptAllTest extends TestCase { + + /** @var DecryptAll */ + protected $instance; + + /** @var Util | \PHPUnit_Framework_MockObject_MockObject */ + protected $util; + + /** @var KeyManager | \PHPUnit_Framework_MockObject_MockObject */ + protected $keyManager; + + /** @var Crypt | \PHPUnit_Framework_MockObject_MockObject */ + protected $crypt; + + /** @var Session | \PHPUnit_Framework_MockObject_MockObject */ + protected $session; + + /** @var QuestionHelper | \PHPUnit_Framework_MockObject_MockObject */ + protected $questionHelper; + + public function setUp() { + parent::setUp(); + + $this->util = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor()->getMock(); + $this->keyManager = $this->getMockBuilder('OCA\Encryption\KeyManager') + ->disableOriginalConstructor()->getMock(); + $this->crypt = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor()->getMock(); + $this->session = $this->getMockBuilder('OCA\Encryption\Session') + ->disableOriginalConstructor()->getMock(); + $this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper') + ->disableOriginalConstructor()->getMock(); + + $this->instance = new DecryptAll( + $this->util, + $this->keyManager, + $this->crypt, + $this->session, + $this->questionHelper + ); + } + + public function testUpdateSession() { + $this->session->expects($this->once())->method('prepareDecryptAll') + ->with('user1', 'key1'); + + $this->invokePrivate($this->instance, 'updateSession', ['user1', 'key1']); + } + + /** + * @dataProvider dataTestGetPrivateKey + * + * @param string $user + * @param string $recoveryKeyId + */ + public function testGetPrivateKey($user, $recoveryKeyId) { + $password = 'passwd'; + $recoveryKey = 'recoveryKey'; + $userKey = 'userKey'; + $unencryptedKey = 'unencryptedKey'; + + $this->keyManager->expects($this->any())->method('getRecoveryKeyId') + ->willReturn($recoveryKeyId); + + if ($user === $recoveryKeyId) { + $this->keyManager->expects($this->once())->method('getSystemPrivateKey') + ->with($recoveryKeyId)->willReturn($recoveryKey); + $this->keyManager->expects($this->never())->method('getPrivateKey'); + $this->crypt->expects($this->once())->method('decryptPrivateKey') + ->with($recoveryKey, $password)->willReturn($unencryptedKey); + } else { + $this->keyManager->expects($this->never())->method('getSystemPrivateKey'); + $this->keyManager->expects($this->once())->method('getPrivateKey') + ->with($user)->willReturn($userKey); + $this->crypt->expects($this->once())->method('decryptPrivateKey') + ->with($userKey, $password, $user)->willReturn($unencryptedKey); + } + + $this->assertSame($unencryptedKey, + $this->invokePrivate($this->instance, 'getPrivateKey', [$user, $password]) + ); + } + + public function dataTestGetPrivateKey() { + return [ + ['user1', 'recoveryKey'], + ['recoveryKeyId', 'recoveryKeyId'] + ]; + } + +} diff --git a/apps/encryption/tests/lib/crypto/encryptionTest.php b/apps/encryption/tests/lib/crypto/encryptionTest.php index f58aa5d3ccb..9e0cb2f09d1 100644 --- a/apps/encryption/tests/lib/crypto/encryptionTest.php +++ b/apps/encryption/tests/lib/crypto/encryptionTest.php @@ -40,6 +40,12 @@ class EncryptionTest extends TestCase { private $encryptAllMock; /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $decryptAllMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $sessionMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ private $cryptMock; /** @var \PHPUnit_Framework_MockObject_MockObject */ @@ -63,9 +69,15 @@ class EncryptionTest extends TestCase { $this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager') ->disableOriginalConstructor() ->getMock(); + $this->sessionMock = $this->getMockBuilder('OCA\Encryption\Session') + ->disableOriginalConstructor() + ->getMock(); $this->encryptAllMock = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') ->disableOriginalConstructor() ->getMock(); + $this->decryptAllMock = $this->getMockBuilder('OCA\Encryption\Crypto\DecryptAll') + ->disableOriginalConstructor() + ->getMock(); $this->loggerMock = $this->getMockBuilder('OCP\ILogger') ->disableOriginalConstructor() ->getMock(); @@ -81,7 +93,9 @@ class EncryptionTest extends TestCase { $this->cryptMock, $this->keyManagerMock, $this->utilMock, + $this->sessionMock, $this->encryptAllMock, + $this->decryptAllMock, $this->loggerMock, $this->l10nMock ); @@ -170,6 +184,16 @@ class EncryptionTest extends TestCase { */ public function testBegin($mode, $header, $legacyCipher, $defaultCipher, $fileKey, $expected) { + $this->sessionMock->expects($this->once()) + ->method('decryptAllModeActivated') + ->willReturn(false); + + $this->sessionMock->expects($this->never())->method('getDecryptAllUid'); + $this->sessionMock->expects($this->never())->method('getDecryptAllKey'); + $this->keyManagerMock->expects($this->never())->method('getEncryptedFileKey'); + $this->keyManagerMock->expects($this->never())->method('getShareKey'); + $this->cryptMock->expects($this->never())->method('multiKeyDecrypt'); + $this->cryptMock->expects($this->any()) ->method('getCipher') ->willReturn($defaultCipher); @@ -209,6 +233,49 @@ class EncryptionTest extends TestCase { ); } + + /** + * 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') + ); + } + /** * @dataProvider dataTestUpdate * @@ -273,4 +340,15 @@ class EncryptionTest extends TestCase { public function testDecrypt() { $this->instance->decrypt('abc'); } + + public function testPrepareDecryptAll() { + $input = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + + $this->decryptAllMock->expects($this->once())->method('prepare') + ->with($input, $output, 'user'); + + $this->instance->prepareDecryptAll($input, $output, 'user'); + } + } |