summaryrefslogtreecommitdiffstats
path: root/apps/encryption
diff options
context:
space:
mode:
authorBjoern Schiessle <schiessle@owncloud.com>2015-09-07 11:38:44 +0200
committerBjoern Schiessle <schiessle@owncloud.com>2015-09-07 16:08:41 +0200
commitacfc7d7c4d4c2daf00ecd61b11eaa9d953868b92 (patch)
tree19752216adf83b38b4e858a1759a98ba1b067931 /apps/encryption
parentc4096767ccf6a88422a474e786b8e4a398ede84e (diff)
downloadnextcloud-server-acfc7d7c4d4c2daf00ecd61b11eaa9d953868b92.tar.gz
nextcloud-server-acfc7d7c4d4c2daf00ecd61b11eaa9d953868b92.zip
enable usage of a master key
Diffstat (limited to 'apps/encryption')
-rw-r--r--apps/encryption/lib/crypto/encryption.php39
-rw-r--r--apps/encryption/lib/keymanager.php80
-rw-r--r--apps/encryption/lib/users/setup.php1
-rw-r--r--apps/encryption/lib/util.php10
-rw-r--r--apps/encryption/tests/lib/KeyManagerTest.php150
-rw-r--r--apps/encryption/tests/lib/UtilTest.php21
-rw-r--r--apps/encryption/tests/lib/users/SetupTest.php2
7 files changed, 276 insertions, 27 deletions
diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php
index 1bd6af2eca7..d2925e1b6be 100644
--- a/apps/encryption/lib/crypto/encryption.php
+++ b/apps/encryption/lib/crypto/encryption.php
@@ -84,6 +84,9 @@ class Encryption implements IEncryptionModule {
/** @var EncryptAll */
private $encryptAll;
+ /** @var bool */
+ private $useMasterPassword;
+
/**
*
* @param Crypt $crypt
@@ -105,6 +108,7 @@ class Encryption implements IEncryptionModule {
$this->encryptAll = $encryptAll;
$this->logger = $logger;
$this->l = $il10n;
+ $this->useMasterPassword = $util->isMasterKeyEnabled();
}
/**
@@ -193,23 +197,26 @@ class Encryption implements IEncryptionModule {
$this->writeCache = '';
}
$publicKeys = array();
- foreach ($this->accessList['users'] as $uid) {
- try {
- $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
- } catch (PublicKeyMissingException $e) {
- $this->logger->warning(
- 'no public key found for user "{uid}", user will not be able to read the file',
- ['app' => 'encryption', 'uid' => $uid]
- );
- // if the public key of the owner is missing we should fail
- if ($uid === $this->user) {
- throw $e;
+ if ($this->useMasterPassword === true) {
+ $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
+ } else {
+ foreach ($this->accessList['users'] as $uid) {
+ try {
+ $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
+ } catch (PublicKeyMissingException $e) {
+ $this->logger->warning(
+ 'no public key found for user "{uid}", user will not be able to read the file',
+ ['app' => 'encryption', 'uid' => $uid]
+ );
+ // if the public key of the owner is missing we should fail
+ if ($uid === $this->user) {
+ throw $e;
+ }
}
}
}
$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->user);
-
$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
$this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
}
@@ -318,8 +325,12 @@ class Encryption implements IEncryptionModule {
if (!empty($fileKey)) {
$publicKeys = array();
- foreach ($accessList['users'] as $user) {
- $publicKeys[$user] = $this->keyManager->getPublicKey($user);
+ if ($this->useMasterPassword === true) {
+ $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
+ } else {
+ foreach ($accessList['users'] as $user) {
+ $publicKeys[$user] = $this->keyManager->getPublicKey($user);
+ }
}
$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $uid);
diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php
index 6c793e5964f..c4507228878 100644
--- a/apps/encryption/lib/keymanager.php
+++ b/apps/encryption/lib/keymanager.php
@@ -55,6 +55,10 @@ class KeyManager {
*/
private $publicShareKeyId;
/**
+ * @var string
+ */
+ private $masterKeyId;
+ /**
* @var string UserID
*/
private $keyId;
@@ -131,10 +135,20 @@ class KeyManager {
$this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId);
}
+ $this->masterKeyId = $this->config->getAppValue('encryption',
+ 'masterKeyId');
+ if (empty($this->masterKeyId)) {
+ $this->masterKeyId = 'master_' . substr(md5(time()), 0, 8);
+ $this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId);
+ }
+
$this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
$this->log = $log;
}
+ /**
+ * check if key pair for public link shares exists, if not we create one
+ */
public function validateShareKey() {
$shareKey = $this->getPublicShareKey();
if (empty($shareKey)) {
@@ -153,6 +167,26 @@ class KeyManager {
}
/**
+ * check if a key pair for the master key exists, if not we create one
+ */
+ public function validateMasterKey() {
+ $masterKey = $this->getPublicMasterKey();
+ if (empty($masterKey)) {
+ $keyPair = $this->crypt->createKeyPair();
+
+ // Save public key
+ $this->keyStorage->setSystemUserKey(
+ $this->masterKeyId . '.publicKey', $keyPair['publicKey'],
+ Encryption::ID);
+
+ // Encrypt private key with system password
+ $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId);
+ $header = $this->crypt->generateHeader();
+ $this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey);
+ }
+ }
+
+ /**
* @return bool
*/
public function recoveryKeyExists() {
@@ -304,8 +338,15 @@ class KeyManager {
$this->session->setStatus(Session::INIT_EXECUTED);
+
try {
- $privateKey = $this->getPrivateKey($uid);
+ if($this->util->isMasterKeyEnabled()) {
+ $uid = $this->getMasterKeyId();
+ $passPhrase = $this->getMasterKeyPassword();
+ $privateKey = $this->getSystemPrivateKey($uid);
+ } else {
+ $privateKey = $this->getPrivateKey($uid);
+ }
$privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid);
} catch (PrivateKeyMissingException $e) {
return false;
@@ -345,6 +386,10 @@ class KeyManager {
public function getFileKey($path, $uid) {
$encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);
+ if ($this->util->isMasterKeyEnabled()) {
+ $uid = $this->getMasterKeyId();
+ }
+
if (is_null($uid)) {
$uid = $this->getPublicShareKeyId();
$shareKey = $this->getShareKey($path, $uid);
@@ -566,4 +611,37 @@ class KeyManager {
return $publicKeys;
}
+
+ /**
+ * get master key password
+ *
+ * @return string
+ * @throws \Exception
+ */
+ protected function getMasterKeyPassword() {
+ $password = $this->config->getSystemValue('secret');
+ if (empty($password)){
+ throw new \Exception('Can not get secret from ownCloud instance');
+ }
+
+ return $password;
+ }
+
+ /**
+ * return master key id
+ *
+ * @return string
+ */
+ public function getMasterKeyId() {
+ return $this->masterKeyId;
+ }
+
+ /**
+ * get public master key
+ *
+ * @return string
+ */
+ public function getPublicMasterKey() {
+ return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID);
+ }
}
diff --git a/apps/encryption/lib/users/setup.php b/apps/encryption/lib/users/setup.php
index 433ea824c9b..d4f7c374547 100644
--- a/apps/encryption/lib/users/setup.php
+++ b/apps/encryption/lib/users/setup.php
@@ -84,6 +84,7 @@ class Setup {
*/
public function setupServerSide($uid, $password) {
$this->keyManager->validateShareKey();
+ $this->keyManager->validateMasterKey();
// Check if user already has keys
if (!$this->keyManager->userHasKeys($uid)) {
return $this->keyManager->storeKeyPair($uid, $password,
diff --git a/apps/encryption/lib/util.php b/apps/encryption/lib/util.php
index fbedc5d6077..e9f916eff38 100644
--- a/apps/encryption/lib/util.php
+++ b/apps/encryption/lib/util.php
@@ -102,6 +102,16 @@ class Util {
}
/**
+ * check if master key is enabled
+ *
+ * @return bool
+ */
+ public function isMasterKeyEnabled() {
+ $userMasterKey = $this->config->getAppValue('encryption', 'useMasterKey', '0');
+ return ($userMasterKey === '1');
+ }
+
+ /**
* @param $enabled
* @return bool
*/
diff --git a/apps/encryption/tests/lib/KeyManagerTest.php b/apps/encryption/tests/lib/KeyManagerTest.php
index 71b00cf254a..8f1da623efb 100644
--- a/apps/encryption/tests/lib/KeyManagerTest.php
+++ b/apps/encryption/tests/lib/KeyManagerTest.php
@@ -27,6 +27,7 @@ namespace OCA\Encryption\Tests;
use OCA\Encryption\KeyManager;
+use OCA\Encryption\Session;
use Test\TestCase;
class KeyManagerTest extends TestCase {
@@ -237,24 +238,62 @@ class KeyManagerTest extends TestCase {
}
+ /**
+ * @dataProvider dataTestInit
+ *
+ * @param bool $useMasterKey
+ */
+ public function testInit($useMasterKey) {
+
+ $instance = $this->getMockBuilder('OCA\Encryption\KeyManager')
+ ->setConstructorArgs(
+ [
+ $this->keyStorageMock,
+ $this->cryptMock,
+ $this->configMock,
+ $this->userMock,
+ $this->sessionMock,
+ $this->logMock,
+ $this->utilMock
+ ]
+ )->setMethods(['getMasterKeyId', 'getMasterKeyPassword', 'getSystemPrivateKey', 'getPrivateKey'])
+ ->getMock();
- public function testInit() {
- $this->keyStorageMock->expects($this->any())
- ->method('getUserKey')
- ->with($this->equalTo($this->userId), $this->equalTo('privateKey'))
- ->willReturn('privateKey');
- $this->cryptMock->expects($this->any())
- ->method('decryptPrivateKey')
- ->with($this->equalTo('privateKey'), $this->equalTo('pass'))
- ->willReturn('decryptedPrivateKey');
+ $this->utilMock->expects($this->once())->method('isMasterKeyEnabled')
+ ->willReturn($useMasterKey);
+
+ $this->sessionMock->expects($this->at(0))->method('setStatus')
+ ->with(Session::INIT_EXECUTED);
+
+ $instance->expects($this->any())->method('getMasterKeyId')->willReturn('masterKeyId');
+ $instance->expects($this->any())->method('getMasterKeyPassword')->willReturn('masterKeyPassword');
+ $instance->expects($this->any())->method('getSystemPrivateKey')->with('masterKeyId')->willReturn('privateMasterKey');
+ $instance->expects($this->any())->method('getPrivateKey')->with($this->userId)->willReturn('privateUserKey');
+
+ if($useMasterKey) {
+ $this->cryptMock->expects($this->once())->method('decryptPrivateKey')
+ ->with('privateMasterKey', 'masterKeyPassword', 'masterKeyId')
+ ->willReturn('key');
+ } else {
+ $this->cryptMock->expects($this->once())->method('decryptPrivateKey')
+ ->with('privateUserKey', 'pass', $this->userId)
+ ->willReturn('key');
+ }
+ $this->sessionMock->expects($this->once())->method('setPrivateKey')
+ ->with('key');
- $this->assertTrue(
- $this->instance->init($this->userId, 'pass')
- );
+ $this->assertTrue($instance->init($this->userId, 'pass'));
+ }
+ public function dataTestInit() {
+ return [
+ [true],
+ [false]
+ ];
}
+
public function testSetRecoveryKey() {
$this->keyStorageMock->expects($this->exactly(2))
->method('setSystemUserKey')
@@ -401,5 +440,92 @@ class KeyManagerTest extends TestCase {
);
}
+ public function testGetMasterKeyId() {
+ $this->assertSame('systemKeyId', $this->instance->getMasterKeyId());
+ }
+
+ public function testGetPublicMasterKey() {
+ $this->keyStorageMock->expects($this->once())->method('getSystemUserKey')
+ ->with('systemKeyId.publicKey', \OCA\Encryption\Crypto\Encryption::ID)
+ ->willReturn(true);
+
+ $this->assertTrue(
+ $this->instance->getPublicMasterKey()
+ );
+ }
+
+ public function testGetMasterKeyPassword() {
+ $this->configMock->expects($this->once())->method('getSystemValue')->with('secret')
+ ->willReturn('password');
+
+ $this->assertSame('password',
+ $this->invokePrivate($this->instance, 'getMasterKeyPassword', [])
+ );
+ }
+
+ /**
+ * @expectedException \Exception
+ */
+ public function testGetMasterKeyPasswordException() {
+ $this->configMock->expects($this->once())->method('getSystemValue')->with('secret')
+ ->willReturn('');
+
+ $this->invokePrivate($this->instance, 'getMasterKeyPassword', []);
+ }
+
+ /**
+ * @dataProvider dataTestValidateMasterKey
+ *
+ * @param $masterKey
+ */
+ public function testValidateMasterKey($masterKey) {
+
+ /** @var \OCA\Encryption\KeyManager | \PHPUnit_Framework_MockObject_MockObject $instance */
+ $instance = $this->getMockBuilder('OCA\Encryption\KeyManager')
+ ->setConstructorArgs(
+ [
+ $this->keyStorageMock,
+ $this->cryptMock,
+ $this->configMock,
+ $this->userMock,
+ $this->sessionMock,
+ $this->logMock,
+ $this->utilMock
+ ]
+ )->setMethods(['getPublicMasterKey', 'setSystemPrivateKey', 'getMasterKeyPassword'])
+ ->getMock();
+
+ $instance->expects($this->once())->method('getPublicMasterKey')
+ ->willReturn($masterKey);
+
+ $instance->expects($this->any())->method('getMasterKeyPassword')->willReturn('masterKeyPassword');
+ $this->cryptMock->expects($this->any())->method('generateHeader')->willReturn('header');
+
+ if(empty($masterKey)) {
+ $this->cryptMock->expects($this->once())->method('createKeyPair')
+ ->willReturn(['publicKey' => 'public', 'privateKey' => 'private']);
+ $this->keyStorageMock->expects($this->once())->method('setSystemUserKey')
+ ->with('systemKeyId.publicKey', 'public', \OCA\Encryption\Crypto\Encryption::ID);
+ $this->cryptMock->expects($this->once())->method('encryptPrivateKey')
+ ->with('private', 'masterKeyPassword', 'systemKeyId')
+ ->willReturn('EncryptedKey');
+ $instance->expects($this->once())->method('setSystemPrivateKey')
+ ->with('systemKeyId', 'headerEncryptedKey');
+ } else {
+ $this->cryptMock->expects($this->never())->method('createKeyPair');
+ $this->keyStorageMock->expects($this->never())->method('setSystemUserKey');
+ $this->cryptMock->expects($this->never())->method('encryptPrivateKey');
+ $instance->expects($this->never())->method('setSystemPrivateKey');
+ }
+
+ $instance->validateMasterKey();
+ }
+
+ public function dataTestValidateMasterKey() {
+ return [
+ ['masterKey'],
+ ['']
+ ];
+ }
}
diff --git a/apps/encryption/tests/lib/UtilTest.php b/apps/encryption/tests/lib/UtilTest.php
index e75e8ea36b4..9988ff93f43 100644
--- a/apps/encryption/tests/lib/UtilTest.php
+++ b/apps/encryption/tests/lib/UtilTest.php
@@ -132,4 +132,25 @@ class UtilTest extends TestCase {
return $default ?: null;
}
+ /**
+ * @dataProvider dataTestIsMasterKeyEnabled
+ *
+ * @param string $value
+ * @param bool $expect
+ */
+ public function testIsMasterKeyEnabled($value, $expect) {
+ $this->configMock->expects($this->once())->method('getAppValue')
+ ->with('encryption', 'useMasterKey', '0')->willReturn($value);
+ $this->assertSame($expect,
+ $this->instance->isMasterKeyEnabled()
+ );
+ }
+
+ public function dataTestIsMasterKeyEnabled() {
+ return [
+ ['0', false],
+ ['1', true]
+ ];
+ }
+
}
diff --git a/apps/encryption/tests/lib/users/SetupTest.php b/apps/encryption/tests/lib/users/SetupTest.php
index e6936c5c12e..bca3ff58b07 100644
--- a/apps/encryption/tests/lib/users/SetupTest.php
+++ b/apps/encryption/tests/lib/users/SetupTest.php
@@ -43,6 +43,8 @@ class SetupTest extends TestCase {
private $instance;
public function testSetupServerSide() {
+ $this->keyManagerMock->expects($this->exactly(2))->method('validateShareKey');
+ $this->keyManagerMock->expects($this->exactly(2))->method('validateMasterKey');
$this->keyManagerMock->expects($this->exactly(2))
->method('userHasKeys')
->with('admin')