Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2014-12-01 13:07:57 +0300
committerVincent Petry <pvince81@owncloud.com>2014-12-01 13:07:57 +0300
commit3917383ceff739763b8bb48544b858bd18633732 (patch)
treee356663f384c572754e5eb18198156fe3b5e4fe1
parent46a4af1dc2a35d9b5ab96ed7df6a7deb10d69ed6 (diff)
parent74b68e10e452faf293b97adb99cb1908b88651ce (diff)
Merge pull request #11747 from owncloud/storeCredentialsOnlyInSessionIfRequired
Store credentials only in session if required
-rw-r--r--apps/files_external/lib/smb_oc.php30
-rw-r--r--config/config.sample.php6
-rw-r--r--lib/private/repair.php4
-rw-r--r--lib/private/security/crypto.php130
-rw-r--r--lib/private/security/stringutils.php46
-rw-r--r--lib/private/server.php13
-rw-r--r--lib/private/setup.php1
-rw-r--r--lib/public/security/icrypto.php46
-rw-r--r--lib/public/security/stringutils.php25
-rw-r--r--lib/repair/repairconfig.php11
-rw-r--r--tests/lib/security/crypto.php70
-rw-r--r--tests/lib/security/stringutils.php38
12 files changed, 416 insertions, 4 deletions
diff --git a/apps/files_external/lib/smb_oc.php b/apps/files_external/lib/smb_oc.php
index 0c79c06c5df..8ead750d083 100644
--- a/apps/files_external/lib/smb_oc.php
+++ b/apps/files_external/lib/smb_oc.php
@@ -13,12 +13,16 @@ require_once __DIR__ . '/../3rdparty/smb4php/smb.php';
class SMB_OC extends \OC\Files\Storage\SMB {
private $username_as_share;
+ /**
+ * @param array $params
+ * @throws \Exception
+ */
public function __construct($params) {
if (isset($params['host']) && \OC::$session->exists('smb-credentials')) {
$host=$params['host'];
$this->username_as_share = ($params['username_as_share'] === 'true');
- $params_auth = \OC::$session->get('smb-credentials');
+ $params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$session->get('smb-credentials')), true);
$user = \OC::$session->get('loginname');
$password = $params_auth['password'];
@@ -44,14 +48,34 @@ class SMB_OC extends \OC\Files\Storage\SMB {
}
}
- public static function login( $params ) {
- \OC::$session->set('smb-credentials', $params);
+ /**
+ * Intercepts the user credentials on login and stores them
+ * encrypted inside the session if SMB_OC storage is enabled.
+ * @param array $params
+ */
+ public static function login($params) {
+ $mountpoints = \OC_Mount_Config::getAbsoluteMountPoints($params['uid']);
+ $mountpointClasses = array();
+ foreach($mountpoints as $mountpoint) {
+ $mountpointClasses[$mountpoint['class']] = true;
+ }
+ if(isset($mountpointClasses['\OC\Files\Storage\SMB_OC'])) {
+ \OC::$session->set('smb-credentials', \OC::$server->getCrypto()->encrypt(json_encode($params)));
+ }
}
+ /**
+ * @param string $path
+ * @return boolean
+ */
public function isSharable($path) {
return false;
}
+ /**
+ * @param bool $isPersonal
+ * @return bool
+ */
public function test($isPersonal = true) {
if ($isPersonal) {
if ($this->stat('')) {
diff --git a/config/config.sample.php b/config/config.sample.php
index a32c41787b6..c073d945675 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -834,4 +834,10 @@ $CONFIG = array(
"style-src 'self' 'unsafe-inline'; frame-src *; img-src *; ".
"font-src 'self' data:; media-src *",
+/**
+ * Secret used by ownCloud for various purposes, e.g. to encrypt data. If you
+ * lose this string there will be data corruption.
+ */
+'secret' => 'ICertainlyShouldHaveChangedTheDefaultSecret',
+
);
diff --git a/lib/private/repair.php b/lib/private/repair.php
index 64c50bb0f43..936657ed7d5 100644
--- a/lib/private/repair.php
+++ b/lib/private/repair.php
@@ -10,6 +10,7 @@ namespace OC;
use OC\Hooks\BasicEmitter;
use OC\Hooks\Emitter;
+use OC\Repair\RepairConfig;
class Repair extends BasicEmitter {
/**
@@ -69,7 +70,8 @@ class Repair extends BasicEmitter {
*/
public static function getRepairSteps() {
return array(
- new \OC\Repair\RepairMimeTypes()
+ new \OC\Repair\RepairMimeTypes(),
+ new RepairConfig(),
);
}
diff --git a/lib/private/security/crypto.php b/lib/private/security/crypto.php
new file mode 100644
index 00000000000..44fe2fc0326
--- /dev/null
+++ b/lib/private/security/crypto.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+namespace OC\Security;
+
+use Crypt_AES;
+use Crypt_Hash;
+use OCP\Security\ICrypto;
+use OCP\Security\StringUtils;
+use OCP\IConfig;
+
+/**
+ * Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
+ * it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
+ *
+ * Usage:
+ * $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
+ * $encryptWithCustompassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
+ *
+ * @package OC\Security
+ */
+class Crypto implements ICrypto {
+ /** @var Crypt_AES $cipher */
+ private $cipher;
+ /** @var int */
+ private $ivLength = 16;
+ /** @var IConfig */
+ private $config;
+
+ /**
+ * @param IConfig $config
+ */
+ function __construct(IConfig $config) {
+ $this->cipher = new Crypt_AES();
+ $this->config = $config;
+ }
+
+ /**
+ * Custom implementation of hex2bin since the function is only available starting
+ * with PHP 5.4
+ *
+ * @TODO Remove this once 5.3 support for ownCloud is dropped
+ * @param $message
+ * @return string
+ */
+ protected static function hexToBin($message) {
+ if (function_exists('hex2bin')) {
+ return hex2bin($message);
+ }
+
+ return pack("H*", $message);
+ }
+
+ /**
+ * @param string $message The message to authenticate
+ * @param string $password Password to use (defaults to `secret` in config.php)
+ * @return string Calculated HMAC
+ */
+ public function calculateHMAC($message, $password = '') {
+ if($password === '') {
+ $password = $this->config->getSystemValue('secret');
+ }
+
+ // Append an "a" behind the password and hash it to prevent reusing the same password as for encryption
+ $password = hash('sha512', $password . 'a');
+
+ $hash = new Crypt_Hash('sha512');
+ $hash->setKey($password);
+ return $hash->hash($message);
+ }
+
+ /**
+ * Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
+ * @param string $plaintext
+ * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
+ * @return string Authenticated ciphertext
+ */
+ public function encrypt($plaintext, $password = '') {
+ if($password === '') {
+ $password = $this->config->getSystemValue('secret');
+ }
+ $this->cipher->setPassword($password);
+
+ $iv = \OC_Util::generateRandomBytes($this->ivLength);
+ $this->cipher->setIV($iv);
+
+ $ciphertext = bin2hex($this->cipher->encrypt($plaintext));
+ $hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password));
+
+ return $ciphertext.'|'.$iv.'|'.$hmac;
+ }
+
+ /**
+ * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
+ * @param string $authenticatedCiphertext
+ * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
+ * @return string plaintext
+ * @throws \Exception If the HMAC does not match
+ */
+ public function decrypt($authenticatedCiphertext, $password = '') {
+ if($password === '') {
+ $password = $this->config->getSystemValue('secret');
+ }
+ $this->cipher->setPassword($password);
+
+ $parts = explode('|', $authenticatedCiphertext);
+ if(sizeof($parts) !== 3) {
+ throw new \Exception('Authenticated ciphertext could not be decoded.');
+ }
+
+ $ciphertext = self::hexToBin($parts[0]);
+ $iv = $parts[1];
+ $hmac = self::hexToBin($parts[2]);
+
+ $this->cipher->setIV($iv);
+
+ if(!StringUtils::equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) {
+ throw new \Exception('HMAC does not match.');
+ }
+
+ return $this->cipher->decrypt($ciphertext);
+ }
+
+} \ No newline at end of file
diff --git a/lib/private/security/stringutils.php b/lib/private/security/stringutils.php
new file mode 100644
index 00000000000..449883b634f
--- /dev/null
+++ b/lib/private/security/stringutils.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Security;
+
+class StringUtils {
+
+ /**
+ * Compares whether two strings are equal. To prevent guessing of the string
+ * length this is done by comparing two hashes against each other and afterwards
+ * a comparison of the real string to prevent against the unlikely chance of
+ * collisions.
+ *
+ * Be aware that this function may leak whether the string to compare have a different
+ * length.
+ *
+ * @param string $expected The expected value
+ * @param string $input The input to compare against
+ * @return bool True if the two strings are equal, otherwise false.
+ */
+ public static function equals($expected, $input) {
+
+ if(!is_string($expected) || !is_string($input)) {
+ return false;
+ }
+
+ if(function_exists('hash_equals')) {
+ return hash_equals($expected, $input);
+ }
+
+ $randomString = \OC_Util::generateRandomBytes(10);
+
+ if(hash('sha512', $expected.$randomString) === hash('sha512', $input.$randomString)) {
+ if($expected === $input) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/lib/private/server.php b/lib/private/server.php
index 790edfc2103..07f80311aea 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -10,6 +10,7 @@ use OC\DB\ConnectionWrapper;
use OC\Files\Node\Root;
use OC\Files\View;
use OCP\IServerContainer;
+use OC\Security\Crypto;
/**
* Class Server
@@ -199,6 +200,9 @@ class Server extends SimpleContainer implements IServerContainer {
$this->registerService('Search', function ($c) {
return new Search();
});
+ $this->registerService('Crypto', function ($c) {
+ return new Crypto(\OC::$server->getConfig());
+ });
$this->registerService('Db', function ($c) {
return new Db();
});
@@ -480,6 +484,15 @@ class Server extends SimpleContainer implements IServerContainer {
}
/**
+ * Returns a Crypto instance
+ *
+ * @return \OCP\Security\ICrypto
+ */
+ function getCrypto() {
+ return $this->query('Crypto');
+ }
+
+ /**
* Returns an instance of the db facade
*
* @return \OCP\IDb
diff --git a/lib/private/setup.php b/lib/private/setup.php
index 6fb5ac2d62d..a18c72db334 100644
--- a/lib/private/setup.php
+++ b/lib/private/setup.php
@@ -181,6 +181,7 @@ class OC_Setup {
//generate a random salt that is used to salt the local user passwords
$salt = OC_Util::generateRandomBytes(30);
OC_Config::setValue('passwordsalt', $salt);
+ OC_Config::setValue('secret', OC_Util::generateRandomBytes(96));
//write the config file
OC_Config::setValue('trusted_domains', $trustedDomains);
diff --git a/lib/public/security/icrypto.php b/lib/public/security/icrypto.php
new file mode 100644
index 00000000000..204935d73ac
--- /dev/null
+++ b/lib/public/security/icrypto.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCP\Security;
+
+/**
+ * Class Crypto provides a high-level encryption layer using AES-CBC. If no key has been provided
+ * it will use the secret defined in config.php as key. Additionally the message will be HMAC'd.
+ *
+ * Usage:
+ * $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText');
+ * $encryptWithCustomPassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password');
+ *
+ * @package OCP\Security
+ */
+interface ICrypto {
+
+ /**
+ * @param string $message The message to authenticate
+ * @param string $password Password to use (defaults to `secret` in config.php)
+ * @return string Calculated HMAC
+ */
+ public function calculateHMAC($message, $password = '');
+
+ /**
+ * Encrypts a value and adds an HMAC (Encrypt-Then-MAC)
+ * @param string $plaintext
+ * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
+ * @return string Authenticated ciphertext
+ */
+ public function encrypt($plaintext, $password = '');
+
+ /**
+ * Decrypts a value and verifies the HMAC (Encrypt-Then-Mac)
+ * @param string $authenticatedCiphertext
+ * @param string $password Password to encrypt, if not specified the secret from config.php will be taken
+ * @return string plaintext
+ * @throws \Exception If the HMAC does not match
+ */
+ public function decrypt($authenticatedCiphertext, $password = '');
+} \ No newline at end of file
diff --git a/lib/public/security/stringutils.php b/lib/public/security/stringutils.php
new file mode 100644
index 00000000000..e74efec4fde
--- /dev/null
+++ b/lib/public/security/stringutils.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+namespace OCP\Security;
+
+class StringUtils {
+ /**
+ * Compares whether two strings are equal. To prevent guessing of the string
+ * length this is done by comparing two hashes against each other and afterwards
+ * a comparison of the real string to prevent against the unlikely chance of
+ * collisions.
+ * @param string $expected The expected value
+ * @param string $input The input to compare against
+ * @return bool True if the two strings are equal, otherwise false.
+ */
+ public static function equals($expected, $input) {
+ return \OC\Security\StringUtils::equals($expected, $input);
+ }
+} \ No newline at end of file
diff --git a/lib/repair/repairconfig.php b/lib/repair/repairconfig.php
index db119b4a25a..e14294b1074 100644
--- a/lib/repair/repairconfig.php
+++ b/lib/repair/repairconfig.php
@@ -31,6 +31,7 @@ class RepairConfig extends BasicEmitter implements RepairStep {
*/
public function run() {
$this->removePortsFromTrustedDomains();
+ $this->addSecret();
}
/**
@@ -51,4 +52,14 @@ class RepairConfig extends BasicEmitter implements RepairStep {
}
\OC::$server->getConfig()->setSystemValue('trusted_domains', $newTrustedDomains);
}
+
+ /**
+ * Adds a secret to config.php
+ */
+ private function addSecret() {
+ if(\OC::$server->getConfig()->getSystemValue('secret', null) === null) {
+ $secret = \OC_Util::generateRandomBytes(96);
+ \OC::$server->getConfig()->setSystemValue('secret', $secret);
+ }
+ }
}
diff --git a/tests/lib/security/crypto.php b/tests/lib/security/crypto.php
new file mode 100644
index 00000000000..5c350b876d5
--- /dev/null
+++ b/tests/lib/security/crypto.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+use \OC\Security\Crypto;
+
+class CryptoTest extends \PHPUnit_Framework_TestCase {
+
+ public function defaultEncryptionProvider()
+ {
+ return array(
+ array('Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'),
+ array(''),
+ array('我看这本书。 我看這本書')
+ );
+ }
+
+ /** @var Crypto */
+ protected $crypto;
+
+ protected function setUp() {
+ $this->crypto = new Crypto(\OC::$server->getConfig());
+ }
+
+ /**
+ * @dataProvider defaultEncryptionProvider
+ */
+ function testDefaultEncrypt($stringToEncrypt) {
+ $ciphertext = $this->crypto->encrypt($stringToEncrypt);
+ $this->assertEquals($stringToEncrypt, $this->crypto->decrypt($ciphertext));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage HMAC does not match.
+ */
+ function testWrongPassword() {
+ $stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.';
+ $ciphertext = $this->crypto->encrypt($stringToEncrypt);
+ $this->crypto->decrypt($ciphertext, 'A wrong password!');
+ }
+
+ function testLaterDecryption() {
+ $stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.';
+ $encryptedString = '44a35023cca2e7a6125e06c29fc4b2ad9d8a33d0873a8b45b0de4ef9284f260c6c46bf25dc62120644c59b8bafe4281ddc47a70c35ae6c29ef7a63d79eefacc297e60b13042ac582733598d0a6b4de37311556bb5c480fd2633de4e6ebafa868c2d1e2d80a5d24f9660360dba4d6e0c8|lhrFgK0zd9U160Wo|a75e57ab701f9124e1113543fd1dc596f21e20d456a0d1e813d5a8aaec9adcb11213788e96598b67fe9486a9f0b99642c18296d0175db44b1ae426e4e91080ee';
+ $this->assertEquals($stringToEncrypt, $this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd'));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage HMAC does not match.
+ */
+ function testWrongIV() {
+ $encryptedString = '560f5436ba864b9f12f7f7ca6d41c327554a6f2c0a160a03316b202af07c65163274993f3a46e7547c07ba89304f00594a2f3bd99f83859097c58049c39d0d4ade10e0de914ff0604961e7c849d0271ed6c0b23f984ba16e7d033e3305fb0910e7b6a2a65c988d17dbee71d8f953684d|d2kdFUspVjC0o0sr|1a5feacf87eaa6869a6abdfba9a296e7bbad45b6ad89f7dce67cdc98e2da5dc4379cc672cc655e52bbf19599bf59482fbea13a73937697fa656bf10f3fc4f1aa';
+ $this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd');
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Authenticated ciphertext could not be decoded.
+ */
+ function testWrongParameters() {
+ $encryptedString = '1|2';
+ $this->crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd');
+ }
+} \ No newline at end of file
diff --git a/tests/lib/security/stringutils.php b/tests/lib/security/stringutils.php
new file mode 100644
index 00000000000..65e4556bdfd
--- /dev/null
+++ b/tests/lib/security/stringutils.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+use \OC\Security\StringUtils;
+
+class StringUtilsTest extends \PHPUnit_Framework_TestCase {
+
+ public function dataProvider()
+ {
+ return array(
+ array('Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.', 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'),
+ array('', ''),
+ array('我看这本书。 我看這本書', '我看这本书。 我看這本書'),
+ array('GpKY9fSnWNJbES99zVGvA', 'GpKY9fSnWNJbES99zVGvA')
+ );
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ function testWrongEquals($string) {
+ $this->assertFalse(StringUtils::equals($string, 'A Completely Wrong String'));
+ $this->assertFalse(StringUtils::equals($string, null));
+ }
+
+ /**
+ * @dataProvider dataProvider
+ */
+ function testTrueEquals($string, $expected) {
+ $this->assertTrue(StringUtils::equals($string, $expected));
+ }
+
+} \ No newline at end of file