aboutsummaryrefslogtreecommitdiffstats
path: root/apps/encryption/lib
diff options
context:
space:
mode:
authorClark Tomlinson <fallen013@gmail.com>2015-03-24 17:29:10 -0400
committerThomas Müller <thomas.mueller@tmit.eu>2015-04-07 13:30:27 +0200
commit0c2f9ca849ef41232511cf576cc9a9de2caa43f0 (patch)
treea75194b573985da4a68684cb5a74c9891db34696 /apps/encryption/lib
parent506222567e71fc0d77fa77ee7805c93fa7655b6c (diff)
downloadnextcloud-server-0c2f9ca849ef41232511cf576cc9a9de2caa43f0.tar.gz
nextcloud-server-0c2f9ca849ef41232511cf576cc9a9de2caa43f0.zip
Updating keystorage movement and fixing hooks
Diffstat (limited to 'apps/encryption/lib')
-rw-r--r--apps/encryption/lib/crypto/Encryption.php17
-rw-r--r--apps/encryption/lib/crypto/crypt.php153
-rw-r--r--apps/encryption/lib/hookmanager.php2
-rw-r--r--apps/encryption/lib/keymanager.php324
-rw-r--r--apps/encryption/lib/migrator.php123
-rw-r--r--apps/encryption/lib/recovery.php58
-rw-r--r--apps/encryption/lib/users/setup.php20
-rw-r--r--apps/encryption/lib/util.php394
8 files changed, 859 insertions, 232 deletions
diff --git a/apps/encryption/lib/crypto/Encryption.php b/apps/encryption/lib/crypto/Encryption.php
index 123581b83ac..b8429d7124e 100644
--- a/apps/encryption/lib/crypto/Encryption.php
+++ b/apps/encryption/lib/crypto/Encryption.php
@@ -18,7 +18,7 @@ class Encryption extends Crypt implements IEncryptionModule {
* @return string defining the technical unique id
*/
public function getId() {
- // TODO: Implement getId() method.
+ return md5($this->getDisplayName());
}
/**
@@ -27,7 +27,7 @@ class Encryption extends Crypt implements IEncryptionModule {
* @return string
*/
public function getDisplayName() {
- // TODO: Implement getDisplayName() method.
+ return 'ownCloud Default Encryption';
}
/**
@@ -44,7 +44,9 @@ class Encryption extends Crypt implements IEncryptionModule {
* or if no additional data is needed return a empty array
*/
public function begin($path, $header, $accessList) {
+
// TODO: Implement begin() method.
+ // return additional header information that needs to be written i.e. cypher used
}
/**
@@ -68,6 +70,7 @@ class Encryption extends Crypt implements IEncryptionModule {
*/
public function encrypt($data) {
// Todo: xxx Update Signature and usages
+ // passphrase is file key decrypted with user private/share key
$this->symmetricEncryptFileContent($data);
}
@@ -113,4 +116,14 @@ class Encryption extends Crypt implements IEncryptionModule {
public function calculateUnencryptedSize($path) {
// TODO: Implement calculateUnencryptedSize() method.
}
+
+ /**
+ * get size of the unencrypted payload per block.
+ * ownCloud read/write files with a block size of 8192 byte
+ *
+ * @return integer
+ */
+ public function getUnencryptedBlockSize() {
+ // TODO: Implement getUnencryptedBlockSize() method.
+ }
}
diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php
index 8018f11a370..f9fe4f9bece 100644
--- a/apps/encryption/lib/crypto/crypt.php
+++ b/apps/encryption/lib/crypto/crypt.php
@@ -25,6 +25,8 @@ namespace OCA\Encryption\Crypto;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Encryption\Exceptions\EncryptionFailedException;
use OC\Encryption\Exceptions\GenericEncryptionException;
+use OCA\Files_Encryption\Exception\MultiKeyDecryptException;
+use OCA\Files_Encryption\Exception\MultiKeyEncryptException;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
@@ -83,12 +85,17 @@ class Crypt {
$res = $this->getOpenSSLPKey();
if (!$res) {
- $log->error("Encryption Library could'nt generate users key-pair for {$this->user->getUID()}", ['app' => 'encryption']);
+ $log->error("Encryption Library could'nt generate users key-pair for {$this->user->getUID()}",
+ ['app' => 'encryption']);
if (openssl_error_string()) {
- $log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(), ['app' => 'encryption']);
+ $log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
+ ['app' => 'encryption']);
}
- } elseif (openssl_pkey_export($res, $privateKey, null, $this->getOpenSSLConfig())) {
+ } elseif (openssl_pkey_export($res,
+ $privateKey,
+ null,
+ $this->getOpenSSLConfig())) {
$keyDetails = openssl_pkey_get_details($res);
$publicKey = $keyDetails['key'];
@@ -97,9 +104,11 @@ class Crypt {
'privateKey' => $privateKey
];
}
- $log->error('Encryption library couldn\'t export users private key, please check your servers openSSL configuration.' . $user->getUID(), ['app' => 'encryption']);
+ $log->error('Encryption library couldn\'t export users private key, please check your servers openSSL configuration.' . $user->getUID(),
+ ['app' => 'encryption']);
if (openssl_error_string()) {
- $log->error('Encryption Library:' . openssl_error_string(), ['app' => 'encryption']);
+ $log->error('Encryption Library:' . openssl_error_string(),
+ ['app' => 'encryption']);
}
return false;
@@ -118,7 +127,9 @@ class Crypt {
*/
private function getOpenSSLConfig() {
$config = ['private_key_bits' => 4096];
- $config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl', []), $config);
+ $config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl',
+ []),
+ $config);
return $config;
}
@@ -131,14 +142,18 @@ class Crypt {
public function symmetricEncryptFileContent($plainContent, $passphrase) {
if (!$plainContent) {
- $this->logger->error('Encryption Library, symmetrical encryption failed no content given', ['app' => 'encryption']);
+ $this->logger->error('Encryption Library, symmetrical encryption failed no content given',
+ ['app' => 'encryption']);
return false;
}
$iv = $this->generateIv();
try {
- $encryptedContent = $this->encrypt($plainContent, $iv, $passphrase, $this->getCipher());
+ $encryptedContent = $this->encrypt($plainContent,
+ $iv,
+ $passphrase,
+ $this->getCipher());
// combine content to encrypt the IV identifier and actual IV
$catFile = $this->concatIV($encryptedContent, $iv);
$padded = $this->addPadding($catFile);
@@ -146,7 +161,8 @@ class Crypt {
return $padded;
} catch (EncryptionFailedException $e) {
$message = 'Could not encrypt file content (code: ' . $e->getCode() . '): ';
- $this->logger->error('files_encryption' . $message . $e->getMessage(), ['app' => 'encryption']);
+ $this->logger->error('files_encryption' . $message . $e->getMessage(),
+ ['app' => 'encryption']);
return false;
}
@@ -161,11 +177,16 @@ class Crypt {
* @throws EncryptionFailedException
*/
private function encrypt($plainContent, $iv, $passphrase = '', $cipher = self::DEFAULT_CIPHER) {
- $encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
+ $encryptedContent = openssl_encrypt($plainContent,
+ $cipher,
+ $passphrase,
+ false,
+ $iv);
if (!$encryptedContent) {
$error = 'Encryption (symmetric) of content failed';
- $this->logger->error($error . openssl_error_string(), ['app' => 'encryption']);
+ $this->logger->error($error . openssl_error_string(),
+ ['app' => 'encryption']);
throw new EncryptionFailedException($error);
}
@@ -177,8 +198,9 @@ class Crypt {
*/
public function getCipher() {
$cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER);
- if ($cipher !== 'AES-256-CFB' || $cipher !== 'AES-128-CFB') {
- $this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER, ['app' => 'encryption']);
+ if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
+ $this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER,
+ ['app' => 'encryption']);
$cipher = self::DEFAULT_CIPHER;
}
@@ -214,10 +236,14 @@ class Crypt {
// If we found a header we need to remove it from the key we want to decrypt
if (!empty($header)) {
- $recoveryKey = substr($recoveryKey, strpos($recoveryKey, self::HEADEREND) + strlen(self::HEADERSTART));
+ $recoveryKey = substr($recoveryKey,
+ strpos($recoveryKey,
+ self::HEADEREND) + strlen(self::HEADERSTART));
}
- $plainKey = $this->symmetricDecryptFileContent($recoveryKey, $password, $cipher);
+ $plainKey = $this->symmetricDecryptFileContent($recoveryKey,
+ $password,
+ $cipher);
// Check if this is a valid private key
$res = openssl_get_privatekey($plainKey);
@@ -246,7 +272,10 @@ class Crypt {
$catFile = $this->splitIv($noPadding);
- $plainContent = $this->decrypt($catFile['encrypted'], $catFile['iv'], $passphrase, $cipher);
+ $plainContent = $this->decrypt($catFile['encrypted'],
+ $catFile['iv'],
+ $passphrase,
+ $cipher);
if ($plainContent) {
return $plainContent;
@@ -296,7 +325,11 @@ class Crypt {
* @throws DecryptionFailedException
*/
private function decrypt($encryptedContent, $iv, $passphrase = '', $cipher = self::DEFAULT_CIPHER) {
- $plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
+ $plainContent = openssl_decrypt($encryptedContent,
+ $cipher,
+ $passphrase,
+ false,
+ $iv);
if ($plainContent) {
return $plainContent;
@@ -317,7 +350,8 @@ class Crypt {
$header = substr($data, 0, $endAt + strlen(self::HEADEREND));
// +1 not to start with an ':' which would result in empty element at the beginning
- $exploded = explode(':', substr($header, strlen(self::HEADERSTART) + 1));
+ $exploded = explode(':',
+ substr($header, strlen(self::HEADERSTART) + 1));
$element = array_shift($exploded);
@@ -339,7 +373,8 @@ class Crypt {
if ($random) {
if (!$strong) {
// If OpenSSL indicates randomness is insecure log error
- $this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()', ['app' => 'encryption']);
+ $this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()',
+ ['app' => 'encryption']);
}
/*
@@ -351,5 +386,85 @@ class Crypt {
// If we ever get here we've failed anyway no need for an else
throw new GenericEncryptionException('Generating IV Failed');
}
+
+ /**
+ * Check if a file's contents contains an IV and is symmetrically encrypted
+ *
+ * @param $content
+ * @return bool
+ */
+ public function isCatFileContent($content) {
+ if (!$content) {
+ return false;
+ }
+
+ $noPadding = $this->removePadding($content);
+
+ // Fetch encryption metadata from end of file
+ $meta = substr($noPadding, -22);
+
+ // Fetch identifier from start of metadata
+ $identifier = substr($meta, 0, 6);
+
+ if ($identifier === '00iv00') {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param $encKeyFile
+ * @param $shareKey
+ * @param $privateKey
+ * @return mixed
+ * @throws MultiKeyDecryptException
+ */
+ public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
+ if (!$encKeyFile) {
+ throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
+ }
+
+ if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey)) {
+ return $plainContent;
+ } else {
+ throw new MultiKeyDecryptException('multikeydecrypt with share key failed');
+ }
+ }
+
+ /**
+ * @param $plainContent
+ * @param array $keyFiles
+ * @return array
+ * @throws MultiKeyEncryptException
+ */
+ public function multiKeyEncrypt($plainContent, array $keyFiles) {
+ // openssl_seal returns false without errors if plaincontent is empty
+ // so trigger our own error
+ if (empty($plainContent)) {
+ throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
+ }
+
+ // Set empty vars to be set by openssl by reference
+ $sealed = '';
+ $shareKeys = [];
+ $mappedShareKeys = [];
+
+ if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles)) {
+ $i = 0;
+
+ // Ensure each shareKey is labelled with its coreesponding keyid
+ foreach ($keyFiles as $userId => $publicKey) {
+ $mappedShareKeys[$userId] = $shareKeys[$i];
+ $i++;
+ }
+
+ return [
+ 'keys' => $mappedShareKeys,
+ 'data' => $sealed
+ ];
+ } else {
+ throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
+ }
+ }
}
diff --git a/apps/encryption/lib/hookmanager.php b/apps/encryption/lib/hookmanager.php
index a535230a6a7..9e6d9cd855c 100644
--- a/apps/encryption/lib/hookmanager.php
+++ b/apps/encryption/lib/hookmanager.php
@@ -54,6 +54,8 @@ class HookManager {
public function fireHooks() {
foreach ($this->hookInstances as $instance) {
/**
+ * Fire off the add hooks method of each instance stored in cache
+ *
* @var $instance IHook
*/
$instance->addHooks();
diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php
index 272bf0849c2..83b24c79b8c 100644
--- a/apps/encryption/lib/keymanager.php
+++ b/apps/encryption/lib/keymanager.php
@@ -22,21 +22,27 @@
namespace OCA\Encryption;
+use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Encryption\Exceptions\PrivateKeyMissingException;
use OC\Encryption\Exceptions\PublicKeyMissingException;
use OCA\Encryption\Crypto\Crypt;
-use OCP\Encryption\IKeyStorage;
+use OCP\Encryption\Keys\IStorage;
+use OCP\ICache;
+use OCP\ICacheFactory;
use OCP\IConfig;
-use OCP\IUser;
+use OCP\ILogger;
use OCP\IUserSession;
class KeyManager {
/**
- * @var IKeyStorage
+ * @var ICache
+ */
+ public static $cacheFactory;
+ /**
+ * @var IStorage
*/
private $keyStorage;
-
/**
* @var Crypt
*/
@@ -53,35 +59,143 @@ class KeyManager {
* @var string UserID
*/
private $keyId;
+ /**
+ * @var string
+ */
+ private $publicKeyId = 'public';
+ /**
+ * @var string
+ */
+ private $privateKeyId = 'private';
/**
* @var string
*/
- private $publicKeyId = '.public';
+ private $shareKeyId = 'sharekey';
+
/**
* @var string
*/
- private $privateKeyId = '.private';
+ private $fileKeyId = 'filekey';
/**
* @var IConfig
*/
private $config;
+ /**
+ * @var ILogger
+ */
+ private $log;
/**
- * @param IKeyStorage $keyStorage
+ * @param IStorage $keyStorage
* @param Crypt $crypt
* @param IConfig $config
* @param IUserSession $userSession
+ * @param ICacheFactory $cacheFactory
+ * @param ILogger $log
*/
- public function __construct(IKeyStorage $keyStorage, Crypt $crypt, IConfig $config, IUserSession $userSession) {
+ public function __construct(IStorage $keyStorage, Crypt $crypt, IConfig $config, IUserSession $userSession, ICacheFactory $cacheFactory, ILogger $log) {
$this->keyStorage = $keyStorage;
$this->crypt = $crypt;
$this->config = $config;
- $this->recoveryKeyId = $this->config->getAppValue('encryption', 'recoveryKeyId');
- $this->publicShareKeyId = $this->config->getAppValue('encryption', 'publicShareKeyId');
+ $this->recoveryKeyId = $this->config->getAppValue('encryption',
+ 'recoveryKeyId');
+ $this->publicShareKeyId = $this->config->getAppValue('encryption',
+ 'publicShareKeyId');
$this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
+ self::$cacheFactory = $cacheFactory;
+ self::$cacheFactory = self::$cacheFactory->create('encryption');
+ $this->log = $log;
+ }
+
+ /**
+ * @return bool
+ */
+ public function recoveryKeyExists() {
+ return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
+ }
+
+ /**
+ * @param $password
+ * @return bool
+ */
+ public function checkRecoveryPassword($password) {
+ $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
+ $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey,
+ $password);
+
+ if ($decryptedRecoveryKey) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param string $uid
+ * @param string $password
+ * @param string $keyPair
+ * @return bool
+ */
+ public function storeKeyPair($uid, $password, $keyPair) {
+ // Save Public Key
+ $this->setPublicKey($uid, $keyPair['publicKey']);
+
+ $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'],
+ $password);
+
+ if ($encryptedKey) {
+ $this->setPrivateKey($uid, $encryptedKey);
+ $this->config->setAppValue('encryption', 'recoveryAdminEnabled', 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param $userId
+ * @param $key
+ * @return bool
+ */
+ public function setPublicKey($userId, $key) {
+ return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key);
+ }
+
+ /**
+ * @param $userId
+ * @param $key
+ * @return bool
+ */
+ public function setPrivateKey($userId, $key) {
+ return $this->keyStorage->setUserKey($userId,
+ $this->privateKeyId,
+ $key);
+ }
+
+ /**
+ * Decrypt private key and store it
+ *
+ * @param string $uid userid
+ * @param string $passPhrase users password
+ * @return ICache
+ */
+ public function init($uid, $passPhrase) {
+ try {
+ $privateKey = $this->getPrivateKey($uid);
+ $privateKey = $this->crypt->decryptPrivateKey($privateKey,
+ $passPhrase);
+ } catch (PrivateKeyMissingException $e) {
+ return false;
+ } catch (DecryptionFailedException $e) {
+ return false;
+ }
+
+ self::$cacheFactory->set('privateKey', $privateKey);
+ self::$cacheFactory->set('initStatus', true);
+
+
+ return self::$cacheFactory;
}
/**
@@ -90,7 +204,8 @@ class KeyManager {
* @throws PrivateKeyMissingException
*/
public function getPrivateKey($userId) {
- $privateKey = $this->keyStorage->getUserKey($userId, $this->privateKeyId);
+ $privateKey = $this->keyStorage->getUserKey($userId,
+ $this->privateKeyId);
if (strlen($privateKey) !== 0) {
return $privateKey;
@@ -99,24 +214,105 @@ class KeyManager {
}
/**
- * @param $userId
+ * @param $path
* @return mixed
- * @throws PublicKeyMissingException
*/
- public function getPublicKey($userId) {
- $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
+ public function getFileKey($path) {
+ return $this->keyStorage->getFileKey($path, $this->fileKeyId);
+ }
- if (strlen($publicKey) !== 0) {
- return $publicKey;
- }
- throw new PublicKeyMissingException();
+ /**
+ * @param $path
+ * @return mixed
+ */
+ public function getShareKey($path) {
+ return $this->keyStorage->getFileKey($path, $this->keyId . $this->shareKeyId);
}
/**
+ * Change a user's encryption passphrase
+ *
+ * @param array $params keys: uid, password
+ * @param IUserSession $user
+ * @param Util $util
* @return bool
*/
- public function recoveryKeyExists() {
- return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
+ public function setPassphrase($params, IUserSession $user, Util $util) {
+
+ // Only attempt to change passphrase if server-side encryption
+ // is in use (client-side encryption does not have access to
+ // the necessary keys)
+ if ($this->crypt->mode() === 'server') {
+
+ // Get existing decrypted private key
+ $privateKey = self::$cacheFactory->get('privateKey');
+
+ if ($params['uid'] === $user->getUser()->getUID() && $privateKey) {
+
+ // Encrypt private key with new user pwd as passphrase
+ $encryptedPrivateKey = $this->crypt->symmetricEncryptFileContent($privateKey,
+ $params['password']);
+
+ // Save private key
+ if ($encryptedPrivateKey) {
+ $this->setPrivateKey($user->getUser()->getUID(),
+ $encryptedPrivateKey);
+ } else {
+ $this->log->error('Encryption could not update users encryption password');
+ }
+
+ // NOTE: Session does not need to be updated as the
+ // private key has not changed, only the passphrase
+ // used to decrypt it has changed
+
+
+ } else { // admin changed the password for a different user, create new keys and reencrypt file keys
+
+ $user = $params['uid'];
+ $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
+
+ // we generate new keys if...
+ // ...we have a recovery password and the user enabled the recovery key
+ // ...encryption was activated for the first time (no keys exists)
+ // ...the user doesn't have any files
+ if (($util->recoveryEnabledForUser() && $recoveryPassword)
+
+ || !$this->userHasKeys($user)
+ || !$util->userHasFiles($user)
+ ) {
+
+ // backup old keys
+ $this->backupAllKeys('recovery');
+
+ $newUserPassword = $params['password'];
+
+ $keypair = $this->crypt->createKeyPair();
+
+ // Disable encryption proxy to prevent recursive calls
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
+
+ // Save public key
+ $this->setPublicKey($user, $keypair['publicKey']);
+
+ // Encrypt private key with new password
+ $encryptedKey = $this->crypt->symmetricEncryptFileContent($keypair['privateKey'],
+ $newUserPassword);
+
+ if ($encryptedKey) {
+ $this->setPrivateKey($user, $encryptedKey);
+
+ if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
+ $util->recoverUsersFiles($recoveryPassword);
+ }
+ } else {
+ $this->log->error('Encryption Could not update users encryption password');
+ }
+
+ \OC_FileProxy::$enabled = $proxyStatus;
+ }
+ }
+ }
}
/**
@@ -136,82 +332,70 @@ class KeyManager {
}
/**
- * @param $password
- * @return bool
+ * @param $userId
+ * @return mixed
+ * @throws PublicKeyMissingException
*/
- public function checkRecoveryPassword($password) {
- $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
- $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
+ public function getPublicKey($userId) {
+ $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
- if ($decryptedRecoveryKey) {
- return true;
+ if (strlen($publicKey) !== 0) {
+ return $publicKey;
}
- return false;
+ throw new PublicKeyMissingException();
}
/**
- * @param $userId
- * @param $key
- * @return bool
+ * @param $purpose
+ * @param bool $timestamp
+ * @param bool $includeUserKeys
*/
- public function setPublicKey($userId, $key) {
- return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key);
+ public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
+// $backupDir = $this->keyStorage->;
}
/**
- * @param $userId
- * @param $key
- * @return bool
+ * @param string $uid
*/
- public function setPrivateKey($userId, $key) {
- return $this->keyStorage->setUserKey($userId, $this->privateKeyId, $key);
+ public function replaceUserKeys($uid) {
+ $this->backupAllKeys('password_reset');
+ $this->deletePublicKey($uid);
+ $this->deletePrivateKey($uid);
}
-
/**
- * @param $password
- * @param $keyPair
+ * @param $uid
* @return bool
*/
- public function storeKeyPair($password, $keyPair) {
- // Save Public Key
- $this->setPublicKey($this->keyId, $keyPair['publicKey']);
-
- $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], $password);
-
- if ($encryptedKey) {
- $this->setPrivateKey($this->keyId, $encryptedKey);
- $this->config->setAppValue('encryption', 'recoveryAdminEnabled', 1);
- return true;
- }
- return false;
+ public function deletePublicKey($uid) {
+ return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId);
}
/**
+ * @param $uid
* @return bool
*/
- public function ready() {
- return $this->keyStorage->ready();
+ private function deletePrivateKey($uid) {
+ return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId);
}
-
/**
- * @return \OCP\ICache
- * @throws PrivateKeyMissingException
+ * @param array $userIds
+ * @return array
+ * @throws PublicKeyMissingException
*/
- public function init() {
- try {
- $privateKey = $this->getPrivateKey($this->keyId);
- } catch (PrivateKeyMissingException $e) {
- return false;
- }
+ public function getPublicKeys(array $userIds) {
+ $keys = [];
- $cache = \OC::$server->getMemCacheFactory();
+ foreach ($userIds as $userId) {
+ try {
+ $keys[$userId] = $this->getPublicKey($userId);
+ } catch (PublicKeyMissingException $e) {
+ continue;
+ }
+ }
- $cacheInstance = $cache->create('Encryption');
- $cacheInstance->set('privateKey', $privateKey);
+ return $keys;
- return $cacheInstance;
}
-
}
diff --git a/apps/encryption/lib/migrator.php b/apps/encryption/lib/migrator.php
deleted file mode 100644
index 8f7823cb1ae..00000000000
--- a/apps/encryption/lib/migrator.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * @author Clark Tomlinson <fallen013@gmail.com>
- * @since 3/9/15, 2:44 PM
- * @link http:/www.clarkt.com
- * @copyright Clark Tomlinson © 2015
- *
- */
-
-namespace OCA\Encryption;
-
-
-use OCA\Encryption\Crypto\Crypt;
-use OCP\IConfig;
-use OCP\ILogger;
-use OCP\IUserManager;
-use OCP\IUserSession;
-use OCP\PreConditionNotMetException;
-
-class Migrator {
-
- /**
- * @var bool
- */
- private $status = false;
- /**
- * @var IUserManager
- */
- private $user;
- /**
- * @var IConfig
- */
- private $config;
- /**
- * @var string
- */
- public static $migrationOpen = '0';
- /**
- * @var string
- */
- public static $migrationInProgress = '-1';
- /**
- * @var string
- */
- public static $migrationComplete = '1';
- /**
- * @var IUserManager
- */
- private $userManager;
- /**
- * @var ILogger
- */
- private $log;
- /**
- * @var Crypt
- */
- private $crypt;
-
- /**
- * Migrator constructor.
- *
- * @param IUserSession $userSession
- * @param IConfig $config
- * @param IUserManager $userManager
- * @param ILogger $log
- * @param Crypt $crypt
- */
- public function __construct(IUserSession $userSession, IConfig $config, IUserManager $userManager, ILogger $log, Crypt $crypt) {
- $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false;
- $this->config = $config;
- $this->userManager = $userManager;
- $this->log = $log;
- $this->crypt = $crypt;
- }
-
- /**
- * @param $userId
- * @return bool|string
- */
- public function getStatus($userId) {
- if ($this->userManager->userExists($userId)) {
- $this->status = $this->config->getUserValue($userId, 'encryption', 'migrationStatus', false);
-
- if (!$this->status) {
- $this->config->setUserValue($userId, 'encryption', 'migrationStatus', self::$migrationOpen);
- $this->status = self::$migrationOpen;
- }
- }
-
- return $this->status;
- }
-
- /**
- * @return bool
- */
- public function beginMigration() {
- $status = $this->setMigrationStatus(self::$migrationInProgress, self::$migrationOpen);
-
- if ($status) {
- $this->log->info('Encryption Library Start migration to encrypt for ' . $this->user->getUID());
- return $status;
- }
- $this->log->warning('Encryption Library Could not activate migration for ' . $this->user->getUID() . '. Probably another process already started the inital encryption');
- return $status;
- }
-
- /**
- * @param $status
- * @param bool $preCondition
- * @return bool
- */
- private function setMigrationStatus($status, $preCondition = false) {
- // Convert to string if preCondition is set
- $preCondition = ($preCondition === false) ? false : (string)$preCondition;
-
- try {
- $this->config->setUserValue($this->user->getUID(), 'encryption', 'migrationStatus', (string)$status, $preCondition);
- return true;
- } catch (PreConditionNotMetException $e) {
- return false;
- }
- }
-}
diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php
index 88350e96c53..457184b4b96 100644
--- a/apps/encryption/lib/recovery.php
+++ b/apps/encryption/lib/recovery.php
@@ -22,11 +22,12 @@
namespace OCA\Encryption;
-use OC\Files\View;
use OCA\Encryption\Crypto\Crypt;
-use OCP\Encryption\IKeyStorage;
+use OCP\Encryption\Keys\IStorage;
use OCP\IConfig;
use OCP\IUser;
+use OCP\IUserSession;
+use OCP\PreConditionNotMetException;
use OCP\Security\ISecureRandom;
class Recovery {
@@ -58,20 +59,20 @@ class Recovery {
private $keyStorage;
/**
- * @param IUser $user
+ * @param IUserSession $user
* @param Crypt $crypt
* @param ISecureRandom $random
* @param KeyManager $keyManager
* @param IConfig $config
- * @param IKeyStorage $keyStorage
+ * @param IStorage $keyStorage
*/
- public function __construct(IUser $user,
+ public function __construct(IUserSession $user,
Crypt $crypt,
ISecureRandom $random,
KeyManager $keyManager,
IConfig $config,
- IKeyStorage $keyStorage) {
- $this->user = $user;
+ IStorage $keyStorage) {
+ $this->user = $user && $user->isLoggedIn() ? $user->getUser() : false;
$this->crypt = $crypt;
$this->random = $random;
$this->keyManager = $keyManager;
@@ -97,7 +98,7 @@ class Recovery {
if (!$keyManager->recoveryKeyExists()) {
$keyPair = $this->crypt->createKeyPair();
- return $this->keyManager->storeKeyPair($password, $keyPair);
+ return $this->keyManager->storeKeyPair($this->user->getUID(), $password, $keyPair);
}
if ($keyManager->checkRecoveryPassword($password)) {
@@ -131,4 +132,45 @@ class Recovery {
// No idea new way to do this....
}
+ /**
+ * @return bool
+ */
+ public function recoveryEnabledForUser() {
+ $recoveryMode = $this->config->getUserValue($this->user->getUID(),
+ 'encryption',
+ 'recoveryEnabled',
+ 0);
+
+ return ($recoveryMode === '1');
+ }
+ /**
+ * @param $enabled
+ * @return bool
+ */
+ public function setRecoveryForUser($enabled) {
+ $value = $enabled ? '1' : '0';
+
+ try {
+ $this->config->setUserValue($this->user->getUID(),
+ 'encryption',
+ 'recoveryEnabled',
+ $value);
+ return true;
+ } catch (PreConditionNotMetException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param $recoveryPassword
+ */
+ public function recoverUsersFiles($recoveryPassword) {
+ // todo: get system private key here
+// $this->keyManager->get
+ $privateKey = $this->crypt->decryptPrivateKey($encryptedKey,
+ $recoveryPassword);
+
+ $this->recoverAllFiles('/', $privateKey);
+ }
+
}
diff --git a/apps/encryption/lib/users/setup.php b/apps/encryption/lib/users/setup.php
index 123d6973be9..662a4b4b6af 100644
--- a/apps/encryption/lib/users/setup.php
+++ b/apps/encryption/lib/users/setup.php
@@ -39,24 +39,24 @@ class Setup extends \OCA\Encryption\Setup {
}
/**
- * @param $password
+ * @param $uid userid
+ * @param $password user password
* @return bool
*/
- public function setupUser($password) {
- if ($this->keyManager->ready()) {
- $this->logger->debug('Encryption Library: User Account ' . $this->user->getUID() . ' Is not ready for encryption; configuration started');
- return $this->setupServerSide($password);
- }
+ public function setupUser($uid, $password) {
+ return $this->setupServerSide($uid, $password);
}
/**
- * @param $password
+ * @param $uid userid
+ * @param $password user password
* @return bool
*/
- private function setupServerSide($password) {
+ public function setupServerSide($uid, $password) {
// Check if user already has keys
- if (!$this->keyManager->userHasKeys($this->user->getUID())) {
- return $this->keyManager->storeKeyPair($password, $this->crypt->createKeyPair());
+ if (!$this->keyManager->userHasKeys($uid)) {
+ return $this->keyManager->storeKeyPair($uid, $password,
+ $this->crypt->createKeyPair());
}
return true;
}
diff --git a/apps/encryption/lib/util.php b/apps/encryption/lib/util.php
new file mode 100644
index 00000000000..5cc658a3136
--- /dev/null
+++ b/apps/encryption/lib/util.php
@@ -0,0 +1,394 @@
+<?php
+/**
+ * @author Clark Tomlinson <clark@owncloud.com>
+ * @since 3/17/15, 10:31 AM
+ * @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;
+
+
+use OC\Files\Filesystem;
+use OC\Files\View;
+use OCA\Encryption\Crypto\Crypt;
+use OCA\Files_Versions\Storage;
+use OCP\App;
+use OCP\IConfig;
+use OCP\ILogger;
+use OCP\IUser;
+use OCP\IUserSession;
+use OCP\PreConditionNotMetException;
+use OCP\Share;
+
+class Util {
+ /**
+ * @var View
+ */
+ private $files;
+ /**
+ * @var Filesystem
+ */
+ private $filesystem;
+ /**
+ * @var Crypt
+ */
+ private $crypt;
+ /**
+ * @var KeyManager
+ */
+ private $keyManager;
+ /**
+ * @var ILogger
+ */
+ private $logger;
+ /**
+ * @var bool|IUser
+ */
+ private $user;
+ /**
+ * @var IConfig
+ */
+ private $config;
+
+ /**
+ * Util constructor.
+ *
+ * @param View $files
+ * @param Filesystem $filesystem
+ * @param Crypt $crypt
+ * @param KeyManager $keyManager
+ * @param ILogger $logger
+ * @param IUserSession $userSession
+ * @param IConfig $config
+ */
+ public function __construct(
+ View $files,
+ Filesystem $filesystem,
+ Crypt $crypt,
+ KeyManager $keyManager,
+ ILogger $logger,
+ IUserSession $userSession,
+ IConfig $config
+ ) {
+ $this->files = $files;
+ $this->filesystem = $filesystem;
+ $this->crypt = $crypt;
+ $this->keyManager = $keyManager;
+ $this->logger = $logger;
+ $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false;
+ $this->config = $config;
+ }
+
+ /**
+ * @param $dirPath
+ * @param bool $found
+ * @return array|bool
+ */
+ private function findEncryptedFiles($dirPath, &$found = false) {
+
+ if ($found === false) {
+ $found = [
+ 'plain' => [],
+ 'encrypted' => [],
+ 'broken' => [],
+ ];
+ }
+
+ if ($this->files->is_dir($dirPath) && $handle = $this->files->opendir($dirPath)) {
+ if (is_resource($handle)) {
+ while (($file = readdir($handle) !== false)) {
+ if ($file !== '.' && $file !== '..') {
+
+ // Skip stray part files
+ if ($this->isPartialFilePath($file)) {
+ continue;
+ }
+
+ $filePath = $dirPath . '/' . $this->files->getRelativePath('/' . $file);
+ $relPath = $this->stripUserFilesPath($filePath);
+
+ // If the path is a directory, search its contents
+ if ($this->files->is_dir($filePath)) {
+ // Recurse back
+ $this->findEncryptedFiles($filePath);
+
+ /*
+ * If the path is a file,
+ * determine where they got re-enabled :/
+ */
+ } elseif ($this->files->is_file($filePath)) {
+ $isEncryptedPath = $this->isEncryptedPath($filePath);
+
+ /**
+ * If the file is encrypted
+ *
+ * @note: if the userId is
+ * empty or not set, file will
+ * be detected as plain
+ * @note: this is inefficient;
+ * scanning every file like this
+ * will eat server resources :(
+ * fixMe: xxx find better way
+ */
+ if ($isEncryptedPath) {
+ $fileKey = $this->keyManager->getFileKey($relPath);
+ $shareKey = $this->keyManager->getShareKey($relPath);
+ // If file is encrypted but now file key is available, throw exception
+ if (!$fileKey || !$shareKey) {
+ $this->logger->error('Encryption library, no keys avilable to decrypt the file: ' . $file);
+ $found['broken'][] = [
+ 'name' => $file,
+ 'path' => $filePath,
+ ];
+ } else {
+ $found['encrypted'][] = [
+ 'name' => $file,
+ 'path' => $filePath
+ ];
+ }
+ } else {
+ $found['plain'][] = [
+ 'name' => $file,
+ 'path' => $filePath
+ ];
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * @param $path
+ * @return bool
+ */
+ private function isPartialFilePath($path) {
+ $extension = pathinfo($path, PATHINFO_EXTENSION);
+
+ if ($extension === 'part') {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param $filePath
+ * @return bool|string
+ */
+ private function stripUserFilesPath($filePath) {
+ $split = $this->splitPath($filePath);
+
+ // It is not a file relative to data/user/files
+ if (count($split) < 4 || $split[2] !== 'files') {
+ return false;
+ }
+
+ $sliced = array_slice($split, 3);
+
+ return implode('/', $sliced);
+
+ }
+
+ /**
+ * @param $filePath
+ * @return array
+ */
+ private function splitPath($filePath) {
+ $normalized = $this->filesystem->normalizePath($filePath);
+
+ return explode('/', $normalized);
+ }
+
+ /**
+ * @param $filePath
+ * @return bool
+ */
+ private function isEncryptedPath($filePath) {
+ $data = '';
+
+ // We only need 24 bytes from the last chunck
+ if ($this->files->file_exists($filePath)) {
+ $handle = $this->files->fopen($filePath, 'r');
+ if (is_resource($handle)) {
+ // Suppress fseek warning, we handle the case that fseek
+ // doesn't work in the else branch
+ if (@fseek($handle, -24, SEEK_END) === 0) {
+ $data = fgets($handle);
+ } else {
+ // if fseek failed on the storage we create a local copy
+ // from the file and read this one
+ fclose($handle);
+ $localFile = $this->files->getLocalFile($filePath);
+ $handle = fopen($localFile, 'r');
+
+ if (is_resource($handle) && fseek($handle,
+ -24,
+ SEEK_END) === 0
+ ) {
+ $data = fgets($handle);
+ }
+ }
+ fclose($handle);
+ return $this->crypt->isCatfileContent($data);
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function recoveryEnabledForUser() {
+ $recoveryMode = $this->config->getUserValue($this->user->getUID(),
+ 'encryption',
+ 'recoveryEnabled',
+ 0);
+
+ return ($recoveryMode === '1');
+ }
+
+ /**
+ * @param $enabled
+ * @return bool
+ */
+ public function setRecoveryForUser($enabled) {
+ $value = $enabled ? '1' : '0';
+
+ try {
+ $this->config->setUserValue($this->user->getUID(),
+ 'encryption',
+ 'recoveryEnabled',
+ $value);
+ return true;
+ } catch (PreConditionNotMetException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param $recoveryPassword
+ */
+ public function recoverUsersFiles($recoveryPassword) {
+ // todo: get system private key here
+// $this->keyManager->get
+ $privateKey = $this->crypt->decryptPrivateKey($encryptedKey,
+ $recoveryPassword);
+
+ $this->recoverAllFiles('/', $privateKey);
+ }
+
+ /**
+ * @param string $uid
+ * @return bool
+ */
+ public function userHasFiles($uid) {
+ return $this->files->file_exists($uid . '/files');
+ }
+
+ /**
+ * @param $path
+ * @param $privateKey
+ */
+ private function recoverAllFiles($path, $privateKey) {
+ // Todo relocate to storage
+ $dirContent = $this->files->getDirectoryContent($path);
+
+ foreach ($dirContent as $item) {
+ // Get relative path from encryption/keyfiles
+ $filePath = substr($item['path'], strlen('encryption/keys'));
+ if ($this->files->is_dir($this->user->getUID() . '/files' . '/' . $filePath)) {
+ $this->recoverAllFiles($filePath . '/', $privateKey);
+ } else {
+ $this->recoverFile($filePath, $privateKey);
+ }
+ }
+
+ }
+
+ /**
+ * @param $filePath
+ * @param $privateKey
+ */
+ private function recoverFile($filePath, $privateKey) {
+ $sharingEnabled = Share::isEnabled();
+
+ // Find out who, if anyone, is sharing the file
+ if ($sharingEnabled) {
+ $result = Share::getUsersSharingFile($filePath,
+ $this->user->getUID(),
+ true);
+ $userIds = $result['users'];
+ $userIds[] = 'public';
+ } else {
+ $userIds = [
+ $this->user->getUID(),
+ $this->recoveryKeyId
+ ];
+ }
+ $filteredUids = $this->filterShareReadyUsers($userIds);
+
+ // Decrypt file key
+ $encKeyFile = $this->keyManager->getFileKey($filePath);
+ $shareKey = $this->keyManager->getShareKey($filePath);
+ $plainKeyFile = $this->crypt->multiKeyDecrypt($encKeyFile,
+ $shareKey,
+ $privateKey);
+
+ // Encrypt the file key again to all users, this time with the new publick keyt for the recovered user
+ $userPublicKeys = $this->keyManager->getPublicKeys($filteredUids['ready']);
+ $multiEncryptionKey = $this->crypt->multiKeyEncrypt($plainKeyFile,
+ $userPublicKeys);
+
+ $this->keyManager->setFileKeys($multiEncryptionKey['data']);
+ $this->keyManager->setShareKeys($multiEncryptionKey['keys']);
+ }
+
+ /**
+ * @param $userIds
+ * @return array
+ */
+ private function filterShareReadyUsers($userIds) {
+ // This array will collect the filtered IDs
+ $readyIds = $unreadyIds = [];
+
+ // Loop though users and create array of UIDs that need new keyfiles
+ foreach ($userIds as $user) {
+ // Check that the user is encryption capable, or is the
+ // public system user (for public shares)
+ if ($this->isUserReady($user)) {
+ // construct array of ready UIDs for keymanager
+ $readyIds[] = $user;
+ } else {
+ // Construct array of unready UIDs for keymanager
+ $unreadyIds[] = $user;
+
+ // Log warning; we cant do necessary setup here
+ // because we don't have the user passphrase
+ $this->logger->warning('Encryption Library ' . $this->user->getUID() . ' is not setup for encryption');
+ }
+ }
+ return [
+ 'ready' => $readyIds,
+ 'unready' => $unreadyIds
+ ];
+ }
+
+}