123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- <?php
- /**
- * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
- * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
- * SPDX-License-Identifier: AGPL-3.0-only
- */
- namespace OCA\Encryption\Tests\Crypto;
- use OCA\Encryption\Crypto\Crypt;
- use OCP\Encryption\Exceptions\GenericEncryptionException;
- use OCP\IConfig;
- use OCP\IL10N;
- use OCP\IUserSession;
- use Psr\Log\LoggerInterface;
- use Test\TestCase;
- class CryptTest extends TestCase {
- /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
- private $logger;
- /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
- private $userSession;
- /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
- private $config;
- /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
- private $l;
- /** @var Crypt */
- private $crypt;
- protected function setUp(): void {
- parent::setUp();
- $this->logger = $this->getMockBuilder(LoggerInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->logger->expects($this->any())
- ->method('warning');
- $this->userSession = $this->getMockBuilder(IUserSession::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->config = $this->getMockBuilder(IConfig::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->l = $this->createMock(IL10N::class);
- $this->crypt = new Crypt($this->logger, $this->userSession, $this->config, $this->l);
- }
- /**
- * test getOpenSSLConfig without any additional parameters
- */
- public function testGetOpenSSLConfigBasic(): void {
- $this->config->expects($this->once())
- ->method('getSystemValue')
- ->with($this->equalTo('openssl'), $this->equalTo([]))
- ->willReturn([]);
- $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(): void {
- $this->config->expects($this->once())
- ->method('getSystemValue')
- ->with($this->equalTo('openssl'), $this->equalTo([]))
- ->willReturn(['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): void {
- $this->config->expects($this->once())
- ->method('getSystemValueString')
- ->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
- *
- */
- public function testGenerateHeaderInvalid(): void {
- $this->expectException(\InvalidArgumentException::class);
- $this->crypt->generateHeader('unknown');
- }
- /**
- * @return array
- */
- public function dataTestGenerateHeader() {
- return [
- [null, 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash2:encoding:binary:HEND'],
- ['password', 'HBEGIN:cipher:AES-128-CFB:keyFormat:password:encoding:binary:HEND'],
- ['hash', 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:encoding:binary:HEND']
- ];
- }
- public function testGetCipherWithInvalidCipher(): void {
- $this->config->expects($this->once())
- ->method('getSystemValueString')
- ->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): void {
- $this->config->expects($this->once())
- ->method('getSystemValueString')
- ->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 [
- ['AES-128-CFB', 'AES-128-CFB'],
- ['AES-256-CFB', 'AES-256-CFB'],
- ['AES-128-CTR', 'AES-128-CTR'],
- ['AES-256-CTR', 'AES-256-CTR'],
- ['unknown', 'AES-256-CTR']
- ];
- }
- /**
- * test concatIV()
- */
- public function testConcatIV(): void {
- $result = self::invokePrivate(
- $this->crypt,
- 'concatIV',
- ['content', 'my_iv']);
- $this->assertSame('content00iv00my_iv',
- $result
- );
- }
- /**
- * @dataProvider dataTestSplitMetaData
- */
- public function testSplitMetaData($data, $expected): void {
- $this->config->method('getSystemValueBool')
- ->with('encryption_skip_signature_check', false)
- ->willReturn(true);
- $result = self::invokePrivate($this->crypt, 'splitMetaData', [$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): void {
- $this->config->method('getSystemValueBool')
- ->with('encryption_skip_signature_check', false)
- ->willReturn(true);
- $this->assertSame($expected,
- $this->invokePrivate($this->crypt, 'hasSignature', [$data, 'AES-256-CFB'])
- );
- }
- public function dataTestHasSignature() {
- return [
- ['encryptedContent00iv001234567890123456xx', false],
- ['encryptedContent00iv00123456789012345600sig00e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86xxx', true]
- ];
- }
- /**
- * @dataProvider dataTestHasSignatureFail
- */
- public function testHasSignatureFail($cipher): void {
- $this->expectException(GenericEncryptionException::class);
- $data = 'encryptedContent00iv001234567890123456xx';
- $this->invokePrivate($this->crypt, 'hasSignature', [$data, $cipher]);
- }
- public function dataTestHasSignatureFail() {
- return [
- ['AES-256-CTR'],
- ['aes-256-ctr'],
- ['AES-128-CTR'],
- ['ctr-256-ctr']
- ];
- }
- /**
- * test addPadding()
- */
- public function testAddPadding(): void {
- $result = self::invokePrivate($this->crypt, 'addPadding', ['data']);
- $this->assertSame('dataxxx', $result);
- }
- /**
- * test removePadding()
- *
- * @dataProvider dataProviderRemovePadding
- * @param $data
- * @param $expected
- */
- public function testRemovePadding($data, $expected): void {
- $result = self::invokePrivate($this->crypt, 'removePadding', [$data]);
- $this->assertSame($expected, $result);
- }
- /**
- * data provider for testRemovePadding
- *
- * @return array
- */
- public function dataProviderRemovePadding() {
- return [
- ['dataxx', 'data'],
- ['data', false]
- ];
- }
- /**
- * test parseHeader()
- */
- public function testParseHeader(): void {
- $header = 'HBEGIN:foo:bar:cipher:AES-256-CFB:encoding:binary:HEND';
- $result = self::invokePrivate($this->crypt, 'parseHeader', [$header]);
- $this->assertTrue(is_array($result));
- $this->assertSame(3, count($result));
- $this->assertArrayHasKey('foo', $result);
- $this->assertArrayHasKey('cipher', $result);
- $this->assertArrayHasKey('encoding', $result);
- $this->assertSame('bar', $result['foo']);
- $this->assertSame('AES-256-CFB', $result['cipher']);
- $this->assertSame('binary', $result['encoding']);
- }
- /**
- * test encrypt()
- *
- * @return string
- */
- public function testEncrypt() {
- $decrypted = 'content';
- $password = 'password';
- $cipher = 'AES-256-CTR';
- $iv = self::invokePrivate($this->crypt, 'generateIv');
- $this->assertTrue(is_string($iv));
- $this->assertSame(16, strlen($iv));
- $result = self::invokePrivate($this->crypt, 'encrypt', [$decrypted, $iv, $password, $cipher]);
- $this->assertTrue(is_string($result));
- return [
- 'password' => $password,
- 'iv' => $iv,
- 'cipher' => $cipher,
- 'encrypted' => $result,
- 'decrypted' => $decrypted];
- }
- /**
- * test decrypt()
- *
- * @depends testEncrypt
- */
- public function testDecrypt($data): void {
- $result = self::invokePrivate(
- $this->crypt,
- 'decrypt',
- [$data['encrypted'], $data['iv'], $data['password'], $data['cipher'], true]);
- $this->assertSame($data['decrypted'], $result);
- }
- /**
- * test return values of valid ciphers
- *
- * @dataProvider dataTestGetKeySize
- */
- public function testGetKeySize($cipher, $expected): void {
- $result = $this->invokePrivate($this->crypt, 'getKeySize', [$cipher]);
- $this->assertSame($expected, $result);
- }
- /**
- * test exception if cipher is unknown
- *
- */
- public function testGetKeySizeFailure(): void {
- $this->expectException(\InvalidArgumentException::class);
- $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): void {
- $this->config->method('getSystemValueBool')
- ->withConsecutive(['encryption.legacy_format_support', false],
- ['encryption.use_legacy_base64_encoding', false])
- ->willReturnOnConsecutiveCalls(true, false);
- /** @var Crypt|\PHPUnit\Framework\MockObject\MockObject $crypt */
- $crypt = $this->getMockBuilder(Crypt::class)
- ->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(): void {
- $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'])
- );
- }
- public function testMultiKeyEncrypt(): void {
- $res = openssl_pkey_new();
- openssl_pkey_export($res, $privateKey);
- $publicKeyPem = openssl_pkey_get_details($res)['key'];
- $publicKey = openssl_pkey_get_public($publicKeyPem);
- $shareKeys = $this->crypt->multiKeyEncrypt('content', ['user1' => $publicKey]);
- $this->assertEquals(
- 'content',
- $this->crypt->multiKeyDecrypt($shareKeys['user1'], $privateKey)
- );
- }
- }
|