diff options
Diffstat (limited to 'apps/encryption/tests/Crypto')
-rw-r--r-- | apps/encryption/tests/Crypto/CryptTest.php | 456 | ||||
-rw-r--r-- | apps/encryption/tests/Crypto/DecryptAllTest.php | 133 | ||||
-rw-r--r-- | apps/encryption/tests/Crypto/EncryptAllTest.php | 335 | ||||
-rw-r--r-- | apps/encryption/tests/Crypto/EncryptionTest.php | 424 |
4 files changed, 1348 insertions, 0 deletions
diff --git a/apps/encryption/tests/Crypto/CryptTest.php b/apps/encryption/tests/Crypto/CryptTest.php new file mode 100644 index 00000000000..4b8147ab214 --- /dev/null +++ b/apps/encryption/tests/Crypto/CryptTest.php @@ -0,0 +1,456 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Crypto; + + +use OCA\Encryption\Crypto\Crypt; +use Test\TestCase; + +class CryptTest extends TestCase { + + + /** @var \OCP\ILogger|\PHPUnit_Framework_MockObject_MockObject */ + private $logger; + + /** @var \OCP\IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + + /** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var \OCP\IL10N|\PHPUnit_Framework_MockObject_MockObject */ + private $l; + + /** @var Crypt */ + private $crypt; + + public function setUp() { + parent::setUp(); + + $this->logger = $this->getMockBuilder('OCP\ILogger') + ->disableOriginalConstructor() + ->getMock(); + $this->logger->expects($this->any()) + ->method('warning') + ->willReturn(true); + $this->userSession = $this->getMockBuilder('OCP\IUserSession') + ->disableOriginalConstructor() + ->getMock(); + $this->config = $this->getMockBuilder('OCP\IConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->l = $this->getMock('OCP\IL10N'); + + $this->crypt = new Crypt($this->logger, $this->userSession, $this->config, $this->l); + } + + /** + * test getOpenSSLConfig without any additional parameters + */ + public function testGetOpenSSLConfigBasic() { + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with($this->equalTo('openssl'), $this->equalTo([])) + ->willReturn(array()); + + $result = self::invokePrivate($this->crypt, 'getOpenSSLConfig'); + $this->assertSame(1, count($result)); + $this->assertArrayHasKey('private_key_bits', $result); + $this->assertSame(4096, $result['private_key_bits']); + } + + /** + * test getOpenSSLConfig with additional parameters defined in config.php + */ + public function testGetOpenSSLConfig() { + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with($this->equalTo('openssl'), $this->equalTo([])) + ->willReturn(array('foo' => 'bar', 'private_key_bits' => 1028)); + + $result = self::invokePrivate($this->crypt, 'getOpenSSLConfig'); + $this->assertSame(2, count($result)); + $this->assertArrayHasKey('private_key_bits', $result); + $this->assertArrayHasKey('foo', $result); + $this->assertSame(1028, $result['private_key_bits']); + $this->assertSame('bar', $result['foo']); + } + + + /** + * test generateHeader with valid key formats + * + * @dataProvider dataTestGenerateHeader + */ + public function testGenerateHeader($keyFormat, $expected) { + + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR')) + ->willReturn('AES-128-CFB'); + + if ($keyFormat) { + $result = $this->crypt->generateHeader($keyFormat); + } else { + $result = $this->crypt->generateHeader(); + } + + $this->assertSame($expected, $result); + } + + /** + * test generateHeader with invalid key format + * + * @expectedException \InvalidArgumentException + */ + public function testGenerateHeaderInvalid() { + $this->crypt->generateHeader('unknown'); + } + + /** + * @return array + */ + public function dataTestGenerateHeader() { + return [ + [null, 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'], + ['password', 'HBEGIN:cipher:AES-128-CFB:keyFormat:password:HEND'], + ['hash', 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'] + ]; + } + + public function testGetCipherWithInvalidCipher() { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR')) + ->willReturn('Not-Existing-Cipher'); + $this->logger + ->expects($this->once()) + ->method('warning') + ->with('Unsupported cipher (Not-Existing-Cipher) defined in config.php supported. Falling back to AES-256-CTR'); + + $this->assertSame('AES-256-CTR', $this->crypt->getCipher()); + } + + /** + * @dataProvider dataProviderGetCipher + * @param string $configValue + * @param string $expected + */ + public function testGetCipher($configValue, $expected) { + $this->config->expects($this->once()) + ->method('getSystemValue') + ->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR')) + ->willReturn($configValue); + + $this->assertSame($expected, + $this->crypt->getCipher() + ); + + } + + /** + * data provider for testGetCipher + * + * @return array + */ + public function dataProviderGetCipher() { + return array( + array('AES-128-CFB', 'AES-128-CFB'), + array('AES-256-CFB', 'AES-256-CFB'), + array('AES-128-CTR', 'AES-128-CTR'), + array('AES-256-CTR', 'AES-256-CTR'), + + array('unknown', 'AES-256-CTR') + ); + } + + /** + * test concatIV() + */ + public function testConcatIV() { + + $result = self::invokePrivate( + $this->crypt, + 'concatIV', + array('content', 'my_iv')); + + $this->assertSame('content00iv00my_iv', + $result + ); + } + + /** + * @dataProvider dataTestSplitMetaData + */ + public function testSplitMetaData($data, $expected) { + $result = self::invokePrivate($this->crypt, 'splitMetaData', array($data, 'AES-256-CFB')); + $this->assertTrue(is_array($result)); + $this->assertSame(3, count($result)); + $this->assertArrayHasKey('encrypted', $result); + $this->assertArrayHasKey('iv', $result); + $this->assertArrayHasKey('signature', $result); + $this->assertSame($expected['encrypted'], $result['encrypted']); + $this->assertSame($expected['iv'], $result['iv']); + $this->assertSame($expected['signature'], $result['signature']); + } + + public function dataTestSplitMetaData() { + return [ + ['encryptedContent00iv001234567890123456xx', + ['encrypted' => 'encryptedContent', 'iv' => '1234567890123456', 'signature' => false]], + ['encryptedContent00iv00123456789012345600sig00e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86xxx', + ['encrypted' => 'encryptedContent', 'iv' => '1234567890123456', 'signature' => 'e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86']], + ]; + } + + /** + * @dataProvider dataTestHasSignature + */ + public function testHasSignature($data, $expected) { + $this->assertSame($expected, + $this->invokePrivate($this->crypt, 'hasSignature', array($data, 'AES-256-CFB')) + ); + } + + public function dataTestHasSignature() { + return [ + ['encryptedContent00iv001234567890123456xx', false], + ['encryptedContent00iv00123456789012345600sig00e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86xxx', true] + ]; + } + + /** + * @dataProvider dataTestHasSignatureFail + * @expectedException \OC\HintException + */ + public function testHasSignatureFail($cipher) { + $data = 'encryptedContent00iv001234567890123456xx'; + $this->invokePrivate($this->crypt, 'hasSignature', array($data, $cipher)); + } + + public function dataTestHasSignatureFail() { + return [ + ['AES-256-CTR'], + ['aes-256-ctr'], + ['AES-128-CTR'], + ['ctr-256-ctr'] + ]; + } + + /** + * test addPadding() + */ + public function testAddPadding() { + $result = self::invokePrivate($this->crypt, 'addPadding', array('data')); + $this->assertSame('dataxxx', $result); + } + + /** + * test removePadding() + * + * @dataProvider dataProviderRemovePadding + * @param $data + * @param $expected + */ + public function testRemovePadding($data, $expected) { + $result = self::invokePrivate($this->crypt, 'removePadding', array($data)); + $this->assertSame($expected, $result); + } + + /** + * data provider for testRemovePadding + * + * @return array + */ + public function dataProviderRemovePadding() { + return array( + array('dataxx', 'data'), + array('data', false) + ); + } + + /** + * test parseHeader() + */ + public function testParseHeader() { + + $header= 'HBEGIN:foo:bar:cipher:AES-256-CFB:HEND'; + $result = self::invokePrivate($this->crypt, 'parseHeader', array($header)); + + $this->assertTrue(is_array($result)); + $this->assertSame(2, count($result)); + $this->assertArrayHasKey('foo', $result); + $this->assertArrayHasKey('cipher', $result); + $this->assertSame('bar', $result['foo']); + $this->assertSame('AES-256-CFB', $result['cipher']); + } + + /** + * test encrypt() + * + * @return string + */ + public function testEncrypt() { + + $decrypted = 'content'; + $password = 'password'; + $iv = self::invokePrivate($this->crypt, 'generateIv'); + + $this->assertTrue(is_string($iv)); + $this->assertSame(16, strlen($iv)); + + $result = self::invokePrivate($this->crypt, 'encrypt', array($decrypted, $iv, $password)); + + $this->assertTrue(is_string($result)); + + return array( + 'password' => $password, + 'iv' => $iv, + 'encrypted' => $result, + 'decrypted' => $decrypted); + + } + + /** + * test decrypt() + * + * @depends testEncrypt + */ + public function testDecrypt($data) { + + $result = self::invokePrivate( + $this->crypt, + 'decrypt', + array($data['encrypted'], $data['iv'], $data['password'])); + + $this->assertSame($data['decrypted'], $result); + + } + + /** + * test return values of valid ciphers + * + * @dataProvider dataTestGetKeySize + */ + public function testGetKeySize($cipher, $expected) { + $result = $this->invokePrivate($this->crypt, 'getKeySize', [$cipher]); + $this->assertSame($expected, $result); + } + + /** + * test exception if cipher is unknown + * + * @expectedException \InvalidArgumentException + */ + public function testGetKeySizeFailure() { + $this->invokePrivate($this->crypt, 'getKeySize', ['foo']); + } + + /** + * @return array + */ + public function dataTestGetKeySize() { + return [ + ['AES-256-CFB', 32], + ['AES-128-CFB', 16], + ['AES-256-CTR', 32], + ['AES-128-CTR', 16], + ]; + } + + /** + * @dataProvider dataTestDecryptPrivateKey + */ + public function testDecryptPrivateKey($header, $privateKey, $expectedCipher, $isValidKey, $expected) { + /** @var \OCA\Encryption\Crypto\Crypt | \PHPUnit_Framework_MockObject_MockObject $crypt */ + $crypt = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->setConstructorArgs( + [ + $this->logger, + $this->userSession, + $this->config, + $this->l + ] + ) + ->setMethods( + [ + 'parseHeader', + 'generatePasswordHash', + 'symmetricDecryptFileContent', + 'isValidPrivateKey' + ] + ) + ->getMock(); + + $crypt->expects($this->once())->method('parseHeader')->willReturn($header); + if (isset($header['keyFormat']) && $header['keyFormat'] === 'hash') { + $crypt->expects($this->once())->method('generatePasswordHash')->willReturn('hash'); + $password = 'hash'; + } else { + $crypt->expects($this->never())->method('generatePasswordHash'); + $password = 'password'; + } + + $crypt->expects($this->once())->method('symmetricDecryptFileContent') + ->with('privateKey', $password, $expectedCipher)->willReturn('key'); + $crypt->expects($this->once())->method('isValidPrivateKey')->willReturn($isValidKey); + + $result = $crypt->decryptPrivateKey($privateKey, 'password'); + + $this->assertSame($expected, $result); + } + + /** + * @return array + */ + public function dataTestDecryptPrivateKey() { + return [ + [['cipher' => 'AES-128-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-128-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', false, false], + [['cipher' => 'AES-256-CFB', 'keyFormat' => 'hash'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [['cipher' => 'AES-256-CFB'], 'HBEGIN:HENDprivateKey', 'AES-256-CFB', true, 'key'], + [[], 'privateKey', 'AES-128-CFB', true, 'key'], + ]; + } + + public function testIsValidPrivateKey() { + $res = openssl_pkey_new(); + openssl_pkey_export($res, $privateKey); + + // valid private key + $this->assertTrue( + $this->invokePrivate($this->crypt, 'isValidPrivateKey', [$privateKey]) + ); + + // invalid private key + $this->assertFalse( + $this->invokePrivate($this->crypt, 'isValidPrivateKey', ['foo']) + ); + } + +} diff --git a/apps/encryption/tests/Crypto/DecryptAllTest.php b/apps/encryption/tests/Crypto/DecryptAllTest.php new file mode 100644 index 00000000000..a094f3b3a2d --- /dev/null +++ b/apps/encryption/tests/Crypto/DecryptAllTest.php @@ -0,0 +1,133 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\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, $masterKeyId) { + $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); + } elseif ($user === $masterKeyId) { + $this->keyManager->expects($this->once())->method('getSystemPrivateKey') + ->with($masterKeyId)->willReturn($masterKey); + $this->keyManager->expects($this->never())->method('getPrivateKey'); + $this->crypt->expects($this->once())->method('decryptPrivateKey') + ->with($masterKey, $password, $masterKeyId)->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', 'masterKeyId'], + ['recoveryKeyId', 'recoveryKeyId', 'masterKeyId'], + ['masterKeyId', 'masterKeyId', 'masterKeyId'] + ]; + } + +} diff --git a/apps/encryption/tests/Crypto/EncryptAllTest.php b/apps/encryption/tests/Crypto/EncryptAllTest.php new file mode 100644 index 00000000000..baa80eaaf84 --- /dev/null +++ b/apps/encryption/tests/Crypto/EncryptAllTest.php @@ -0,0 +1,335 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Crypto; + + +use OCA\Encryption\Crypto\EncryptAll; +use Test\TestCase; + +class EncryptAllTest extends TestCase { + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Encryption\KeyManager */ + protected $keyManager; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Encryption\Util */ + protected $util; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IUserManager */ + protected $userManager; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCA\Encryption\Users\Setup */ + protected $setupUser; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OC\Files\View */ + protected $view; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IConfig */ + protected $config; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\Mail\IMailer */ + protected $mailer; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IL10N */ + protected $l; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Helper\QuestionHelper */ + protected $questionHelper; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Input\InputInterface */ + protected $inputInterface; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \Symfony\Component\Console\Output\OutputInterface */ + protected $outputInterface; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\UserInterface */ + protected $userInterface; + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\Security\ISecureRandom */ + protected $secureRandom; + + /** @var EncryptAll */ + protected $encryptAll; + + function setUp() { + parent::setUp(); + $this->setupUser = $this->getMockBuilder('OCA\Encryption\Users\Setup') + ->disableOriginalConstructor()->getMock(); + $this->keyManager = $this->getMockBuilder('OCA\Encryption\KeyManager') + ->disableOriginalConstructor()->getMock(); + $this->util = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor()->getMock(); + $this->userManager = $this->getMockBuilder('OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + $this->view = $this->getMockBuilder('OC\Files\View') + ->disableOriginalConstructor()->getMock(); + $this->config = $this->getMockBuilder('OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + $this->mailer = $this->getMockBuilder('OCP\Mail\IMailer') + ->disableOriginalConstructor()->getMock(); + $this->l = $this->getMockBuilder('OCP\IL10N') + ->disableOriginalConstructor()->getMock(); + $this->questionHelper = $this->getMockBuilder('Symfony\Component\Console\Helper\QuestionHelper') + ->disableOriginalConstructor()->getMock(); + $this->inputInterface = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface') + ->disableOriginalConstructor()->getMock(); + $this->outputInterface = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface') + ->disableOriginalConstructor()->getMock(); + $this->userInterface = $this->getMockBuilder('OCP\UserInterface') + ->disableOriginalConstructor()->getMock(); + + + $this->outputInterface->expects($this->any())->method('getFormatter') + ->willReturn($this->getMock('\Symfony\Component\Console\Formatter\OutputFormatterInterface')); + + $this->userManager->expects($this->any())->method('getBackends')->willReturn([$this->userInterface]); + $this->userInterface->expects($this->any())->method('getUsers')->willReturn(['user1', 'user2']); + + $this->secureRandom = $this->getMockBuilder('OCP\Security\ISecureRandom')->disableOriginalConstructor()->getMock(); + $this->secureRandom->expects($this->any())->method('getMediumStrengthGenerator')->willReturn($this->secureRandom); + $this->secureRandom->expects($this->any())->method('getLowStrengthGenerator')->willReturn($this->secureRandom); + $this->secureRandom->expects($this->any())->method('generate')->willReturn('12345678'); + + + $this->encryptAll = new EncryptAll( + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ); + } + + public function testEncryptAll() { + /** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */ + $encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') + ->setConstructorArgs( + [ + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ] + ) + ->setMethods(['createKeyPairs', 'encryptAllUsersFiles', 'outputPasswords']) + ->getMock(); + + $this->util->expects($this->any())->method('isMasterKeyEnabled')->willReturn(false); + $encryptAll->expects($this->at(0))->method('createKeyPairs')->with(); + $encryptAll->expects($this->at(1))->method('encryptAllUsersFiles')->with(); + $encryptAll->expects($this->at(2))->method('outputPasswords')->with(); + + $encryptAll->encryptAll($this->inputInterface, $this->outputInterface); + + } + + public function testEncryptAllWithMasterKey() { + /** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */ + $encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') + ->setConstructorArgs( + [ + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ] + ) + ->setMethods(['createKeyPairs', 'encryptAllUsersFiles', 'outputPasswords']) + ->getMock(); + + $this->util->expects($this->any())->method('isMasterKeyEnabled')->willReturn(true); + $encryptAll->expects($this->never())->method('createKeyPairs'); + $this->keyManager->expects($this->once())->method('validateMasterKey'); + $encryptAll->expects($this->at(0))->method('encryptAllUsersFiles')->with(); + $encryptAll->expects($this->never())->method('outputPasswords'); + + $encryptAll->encryptAll($this->inputInterface, $this->outputInterface); + + } + + public function testCreateKeyPairs() { + /** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */ + $encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') + ->setConstructorArgs( + [ + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ] + ) + ->setMethods(['setupUserFS', 'generateOneTimePassword']) + ->getMock(); + + + // set protected property $output + $this->invokePrivate($encryptAll, 'output', [$this->outputInterface]); + + $this->keyManager->expects($this->exactly(2))->method('userHasKeys') + ->willReturnCallback( + function ($user) { + if ($user === 'user1') { + return false; + } + return true; + } + ); + + $encryptAll->expects($this->once())->method('setupUserFS')->with('user1'); + $encryptAll->expects($this->once())->method('generateOneTimePassword')->with('user1')->willReturn('password'); + $this->setupUser->expects($this->once())->method('setupUser')->with('user1', 'password'); + + $this->invokePrivate($encryptAll, 'createKeyPairs'); + + $userPasswords = $this->invokePrivate($encryptAll, 'userPasswords'); + + // we only expect the skipped user, because generateOneTimePassword which + // would set the user with the new password was mocked. + // This method will be tested separately + $this->assertSame(1, count($userPasswords)); + $this->assertSame('', $userPasswords['user2']); + } + + public function testEncryptAllUsersFiles() { + /** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */ + $encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') + ->setConstructorArgs( + [ + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ] + ) + ->setMethods(['encryptUsersFiles']) + ->getMock(); + + $this->util->expects($this->any())->method('isMasterKeyEnabled')->willReturn(false); + + // set protected property $output + $this->invokePrivate($encryptAll, 'output', [$this->outputInterface]); + $this->invokePrivate($encryptAll, 'userPasswords', [['user1' => 'pwd1', 'user2' => 'pwd2']]); + + $encryptAll->expects($this->at(0))->method('encryptUsersFiles')->with('user1'); + $encryptAll->expects($this->at(1))->method('encryptUsersFiles')->with('user2'); + + $this->invokePrivate($encryptAll, 'encryptAllUsersFiles'); + + } + + public function testEncryptUsersFiles() { + /** @var EncryptAll | \PHPUnit_Framework_MockObject_MockObject $encryptAll */ + $encryptAll = $this->getMockBuilder('OCA\Encryption\Crypto\EncryptAll') + ->setConstructorArgs( + [ + $this->setupUser, + $this->userManager, + $this->view, + $this->keyManager, + $this->util, + $this->config, + $this->mailer, + $this->l, + $this->questionHelper, + $this->secureRandom + ] + ) + ->setMethods(['encryptFile', 'setupUserFS']) + ->getMock(); + + $this->util->expects($this->any())->method('isMasterKeyEnabled')->willReturn(false); + + $this->view->expects($this->at(0))->method('getDirectoryContent') + ->with('/user1/files')->willReturn( + [ + ['name' => 'foo', 'type'=>'dir'], + ['name' => 'bar', 'type'=>'file'], + ] + ); + + $this->view->expects($this->at(3))->method('getDirectoryContent') + ->with('/user1/files/foo')->willReturn( + [ + ['name' => 'subfile', 'type'=>'file'] + ] + ); + + $this->view->expects($this->any())->method('is_dir') + ->willReturnCallback( + function($path) { + if ($path === '/user1/files/foo') { + return true; + } + return false; + } + ); + + $encryptAll->expects($this->at(1))->method('encryptFile')->with('/user1/files/bar'); + $encryptAll->expects($this->at(2))->method('encryptFile')->with('/user1/files/foo/subfile'); + + $progressBar = $this->getMockBuilder('Symfony\Component\Console\Helper\ProgressBar') + ->disableOriginalConstructor()->getMock(); + + $this->invokePrivate($encryptAll, 'encryptUsersFiles', ['user1', $progressBar, '']); + + } + + public function testGenerateOneTimePassword() { + $password = $this->invokePrivate($this->encryptAll, 'generateOneTimePassword', ['user1']); + $this->assertTrue(is_string($password)); + $this->assertSame(8, strlen($password)); + + $userPasswords = $this->invokePrivate($this->encryptAll, 'userPasswords'); + $this->assertSame(1, count($userPasswords)); + $this->assertSame($password, $userPasswords['user1']); + } + +} diff --git a/apps/encryption/tests/Crypto/EncryptionTest.php b/apps/encryption/tests/Crypto/EncryptionTest.php new file mode 100644 index 00000000000..723c6e31b90 --- /dev/null +++ b/apps/encryption/tests/Crypto/EncryptionTest.php @@ -0,0 +1,424 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Crypto; + +use OCA\Encryption\Exceptions\PublicKeyMissingException; +use Test\TestCase; +use OCA\Encryption\Crypto\Encryption; + +class EncryptionTest extends TestCase { + + /** @var Encryption */ + private $instance; + + /** @var \OCA\Encryption\KeyManager|\PHPUnit_Framework_MockObject_MockObject */ + private $keyManagerMock; + + /** @var \OCA\Encryption\Crypto\EncryptAll|\PHPUnit_Framework_MockObject_MockObject */ + private $encryptAllMock; + + /** @var \OCA\Encryption\Crypto\DecryptAll|\PHPUnit_Framework_MockObject_MockObject */ + private $decryptAllMock; + + /** @var \OCA\Encryption\Session|\PHPUnit_Framework_MockObject_MockObject */ + private $sessionMock; + + /** @var \OCA\Encryption\Crypto\Crypt|\PHPUnit_Framework_MockObject_MockObject */ + private $cryptMock; + + /** @var \OCA\Encryption\Util|\PHPUnit_Framework_MockObject_MockObject */ + private $utilMock; + + /** @var \OCP\ILogger|\PHPUnit_Framework_MockObject_MockObject */ + private $loggerMock; + + /** @var \OCP\IL10N|\PHPUnit_Framework_MockObject_MockObject */ + private $l10nMock; + + /** @var \OCP\Files\Storage|\PHPUnit_Framework_MockObject_MockObject */ + private $storageMock; + + public function setUp() { + parent::setUp(); + + $this->storageMock = $this->getMockBuilder('OCP\Files\Storage') + ->disableOriginalConstructor()->getMock(); + $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor() + ->getMock(); + $this->utilMock = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + $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(); + $this->l10nMock = $this->getMockBuilder('OCP\IL10N') + ->disableOriginalConstructor() + ->getMock(); + $this->l10nMock->expects($this->any()) + ->method('t') + ->with($this->anything()) + ->willReturnArgument(0); + + $this->instance = new Encryption( + $this->cryptMock, + $this->keyManagerMock, + $this->utilMock, + $this->sessionMock, + $this->encryptAllMock, + $this->decryptAllMock, + $this->loggerMock, + $this->l10nMock + ); + + } + + /** + * test if public key from one of the recipients is missing + */ + public function testEndUser1() { + $this->instance->begin('/foo/bar', 'user1', 'r', array(), array('users' => array('user1', 'user2', 'user3'))); + $this->endTest(); + } + + /** + * test if public key from owner is missing + * + * @expectedException \OCA\Encryption\Exceptions\PublicKeyMissingException + */ + public function testEndUser2() { + $this->instance->begin('/foo/bar', 'user2', 'r', array(), array('users' => array('user1', 'user2', 'user3'))); + $this->endTest(); + } + + /** + * common part of testEndUser1 and testEndUser2 + * + * @throws PublicKeyMissingException + */ + public function endTest() { + // prepare internal variables + self::invokePrivate($this->instance, 'isWriteOperation', [true]); + self::invokePrivate($this->instance, 'writeCache', ['']); + + $this->keyManagerMock->expects($this->any()) + ->method('getPublicKey') + ->will($this->returnCallback([$this, 'getPublicKeyCallback'])); + $this->keyManagerMock->expects($this->any()) + ->method('addSystemKeys') + ->will($this->returnCallback([$this, 'addSystemKeysCallback'])); + $this->cryptMock->expects($this->any()) + ->method('multiKeyEncrypt') + ->willReturn(true); + + $this->instance->end('/foo/bar'); + } + + + public function getPublicKeyCallback($uid) { + if ($uid === 'user2') { + throw new PublicKeyMissingException($uid); + } + return $uid; + } + + public function addSystemKeysCallback($accessList, $publicKeys) { + $this->assertSame(2, count($publicKeys)); + $this->assertArrayHasKey('user1', $publicKeys); + $this->assertArrayHasKey('user3', $publicKeys); + return $publicKeys; + } + + /** + * @dataProvider dataProviderForTestGetPathToRealFile + */ + public function testGetPathToRealFile($path, $expected) { + $this->assertSame($expected, + self::invokePrivate($this->instance, 'getPathToRealFile', array($path)) + ); + } + + public function dataProviderForTestGetPathToRealFile() { + return array( + array('/user/files/foo/bar.txt', '/user/files/foo/bar.txt'), + array('/user/files/foo.txt', '/user/files/foo.txt'), + array('/user/files_versions/foo.txt.v543534', '/user/files/foo.txt'), + array('/user/files_versions/foo/bar.txt.v5454', '/user/files/foo/bar.txt'), + ); + } + + /** + * @dataProvider dataTestBegin + */ + 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); + $this->cryptMock->expects($this->any()) + ->method('getLegacyCipher') + ->willReturn($legacyCipher); + if (empty($fileKey)) { + $this->cryptMock->expects($this->once()) + ->method('generateFileKey') + ->willReturn('fileKey'); + } else { + $this->cryptMock->expects($this->never()) + ->method('generateFileKey'); + } + + $this->keyManagerMock->expects($this->once()) + ->method('getFileKey') + ->willReturn($fileKey); + + $result = $this->instance->begin('/user/files/foo.txt', 'user', $mode, $header, []); + + $this->assertArrayHasKey('cipher', $result); + $this->assertSame($expected, $result['cipher']); + if ($mode === 'w') { + $this->assertTrue(self::invokePrivate($this->instance, 'isWriteOperation')); + } else { + $this->assertFalse(self::invokePrivate($this->instance, 'isWriteOperation')); + } + } + + public function dataTestBegin() { + return array( + array('w', ['cipher' => 'myCipher'], 'legacyCipher', 'defaultCipher', 'fileKey', 'defaultCipher'), + array('r', ['cipher' => 'myCipher'], 'legacyCipher', 'defaultCipher', 'fileKey', 'myCipher'), + array('w', [], 'legacyCipher', 'defaultCipher', '', 'defaultCipher'), + array('r', [], 'legacyCipher', 'defaultCipher', 'file_key', 'legacyCipher'), + ); + } + + + /** + * 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 + * + * @param string $fileKey + * @param boolean $expected + */ + public function testUpdate($fileKey, $expected) { + $this->keyManagerMock->expects($this->once()) + ->method('getFileKey')->willReturn($fileKey); + + $this->keyManagerMock->expects($this->any()) + ->method('getPublicKey')->willReturn('publicKey'); + + $this->keyManagerMock->expects($this->any()) + ->method('addSystemKeys') + ->willReturnCallback(function($accessList, $publicKeys) { + return $publicKeys; + }); + + $this->keyManagerMock->expects($this->never())->method('getVersion'); + $this->keyManagerMock->expects($this->never())->method('setVersion'); + + $this->assertSame($expected, + $this->instance->update('path', 'user1', ['users' => ['user1']]) + ); + } + + public function dataTestUpdate() { + return array( + array('', false), + array('fileKey', true) + ); + } + + public function testUpdateNoUsers() { + + $this->invokePrivate($this->instance, 'rememberVersion', [['path' => 2]]); + + $this->keyManagerMock->expects($this->never())->method('getFileKey'); + $this->keyManagerMock->expects($this->never())->method('getPublicKey'); + $this->keyManagerMock->expects($this->never())->method('addSystemKeys'); + $this->keyManagerMock->expects($this->once())->method('setVersion') + ->willReturnCallback(function($path, $version, $view) { + $this->assertSame('path', $path); + $this->assertSame(2, $version); + $this->assertTrue($view instanceof \OC\Files\View); + }); + $this->instance->update('path', 'user1', []); + } + + /** + * Test case if the public key is missing. ownCloud should still encrypt + * the file for the remaining users + */ + public function testUpdateMissingPublicKey() { + $this->keyManagerMock->expects($this->once()) + ->method('getFileKey')->willReturn('fileKey'); + + $this->keyManagerMock->expects($this->any()) + ->method('getPublicKey')->willReturnCallback( + function($user) { + throw new PublicKeyMissingException($user); + } + ); + + $this->keyManagerMock->expects($this->any()) + ->method('addSystemKeys') + ->willReturnCallback(function($accessList, $publicKeys) { + return $publicKeys; + }); + + $this->cryptMock->expects($this->once())->method('multiKeyEncrypt') + ->willReturnCallback( + function($fileKey, $publicKeys) { + $this->assertEmpty($publicKeys); + $this->assertSame('fileKey', $fileKey); + } + ); + + $this->keyManagerMock->expects($this->never())->method('getVersion'); + $this->keyManagerMock->expects($this->never())->method('setVersion'); + + $this->assertTrue( + $this->instance->update('path', 'user1', ['users' => ['user1']]) + ); + } + + /** + * by default the encryption module should encrypt regular files, files in + * files_versions and files in files_trashbin + * + * @dataProvider dataTestShouldEncrypt + */ + public function testShouldEncrypt($path, $shouldEncryptHomeStorage, $isHomeStorage, $expected) { + $this->utilMock->expects($this->once())->method('shouldEncryptHomeStorage') + ->willReturn($shouldEncryptHomeStorage); + + if ($shouldEncryptHomeStorage === false) { + $this->storageMock->expects($this->once())->method('instanceOfStorage') + ->with('\OCP\Files\IHomeStorage')->willReturn($isHomeStorage); + $this->utilMock->expects($this->once())->method('getStorage')->with($path) + ->willReturn($this->storageMock); + } + + $this->assertSame($expected, + $this->instance->shouldEncrypt($path) + ); + } + + public function dataTestShouldEncrypt() { + return array( + array('/user1/files/foo.txt', true, true, true), + array('/user1/files_versions/foo.txt', true, true, true), + array('/user1/files_trashbin/foo.txt', true, true, true), + array('/user1/some_folder/foo.txt', true, true, false), + array('/user1/foo.txt', true, true, false), + array('/user1/files', true, true, false), + array('/user1/files_trashbin', true, true, false), + array('/user1/files_versions', true, true, false), + // test if shouldEncryptHomeStorage is set to false + array('/user1/files/foo.txt', false, true, false), + array('/user1/files_versions/foo.txt', false, false, true), + ); + } + + /** + * @expectedException \OC\Encryption\Exceptions\DecryptionFailedException + * @expectedExceptionMessage Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you. + */ + public function testDecrypt() { + $this->instance->decrypt('abc'); + } + + public function testPrepareDecryptAll() { + /** @var \Symfony\Component\Console\Input\InputInterface $input */ + $input = $this->getMock('Symfony\Component\Console\Input\InputInterface'); + /** @var \Symfony\Component\Console\Output\OutputInterface $output */ + $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'); + } + +} |