0
0
Fork 0
mirror of https://github.com/nextcloud/server.git synced 2025-02-07 18:09:45 +00:00
nextcloud_server/apps/encryption/tests/Crypto/CryptTest.php
Joas Schilling b7a164427c
test: Fix wrongly mocked method
Signed-off-by: Joas Schilling <coding@schilljs.com>
2024-10-21 11:28:53 +02:00

459 lines
13 KiB
PHP

<?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\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 \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;
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() {
$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() {
$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) {
$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() {
$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() {
$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) {
$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() {
$result = self::invokePrivate(
$this->crypt,
'concatIV',
['content', 'my_iv']);
$this->assertSame('content00iv00my_iv',
$result
);
}
/**
* @dataProvider dataTestSplitMetaData
*/
public function testSplitMetaData($data, $expected) {
$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) {
$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) {
$this->expectException(\OCP\Encryption\Exceptions\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() {
$result = self::invokePrivate($this->crypt, 'addPadding', ['data']);
$this->assertSame('dataxxx', $result);
}
/**
* test removePadding()
*
* @dataProvider dataProviderRemovePadding
* @param $data
* @param $expected
*/
public function testRemovePadding($data, $expected) {
$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() {
$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) {
$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) {
$result = $this->invokePrivate($this->crypt, 'getKeySize', [$cipher]);
$this->assertSame($expected, $result);
}
/**
* test exception if cipher is unknown
*
*/
public function testGetKeySizeFailure() {
$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) {
$this->config->method('getSystemValueBool')
->withConsecutive(['encryption.legacy_format_support', false],
['encryption.use_legacy_base64_encoding', false])
->willReturnOnConsecutiveCalls(true, false);
/** @var \OCA\Encryption\Crypto\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() {
$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() {
$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)
);
}
}