[Pkg-owncloud-commits] [owncloud] 44/74: Backport \OC\Security\Crypto to ownCloud 7

David Prévot taffit at moszumanska.debian.org
Tue Dec 2 22:04:36 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit ec853da5ad54af7e6eabb40923784a56330b095a
Author: Lukas Reschke <lukas at owncloud.com>
Date:   Thu Nov 20 16:02:32 2014 +0100

    Backport \OC\Security\Crypto to ownCloud 7
    
    Conflicts:
    	lib/repair/repairconfig.php
---
 config/config.sample.php             |   6 ++
 lib/private/repair.php               |   4 +-
 lib/private/security/crypto.php      | 130 +++++++++++++++++++++++++++++++++++
 lib/private/security/stringutils.php |  46 +++++++++++++
 lib/private/server.php               |  13 ++++
 lib/private/setup.php                |   1 +
 lib/public/security/icrypto.php      |  46 +++++++++++++
 lib/public/security/stringutils.php  |  25 +++++++
 lib/repair/repairconfig.php          |  11 +++
 tests/lib/security/crypto.php        |  70 +++++++++++++++++++
 tests/lib/security/stringutils.php   |  38 ++++++++++
 11 files changed, 389 insertions(+), 1 deletion(-)

diff --git a/config/config.sample.php b/config/config.sample.php
index 11c7a44..dd24c71 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -831,4 +831,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 98bf37f..a65915e 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 0000000..44fe2fc
--- /dev/null
+++ b/lib/private/security/crypto.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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 0000000..449883b
--- /dev/null
+++ b/lib/private/security/stringutils.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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 790edfc..07f8031 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 2d6cede..10322db 100644
--- a/lib/private/setup.php
+++ b/lib/private/setup.php
@@ -179,6 +179,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 0000000..204935d
--- /dev/null
+++ b/lib/public/security/icrypto.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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 0000000..e74efec
--- /dev/null
+++ b/lib/public/security/stringutils.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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 db119b4..e14294b 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 0000000..5c350b8
--- /dev/null
+++ b/tests/lib/security/crypto.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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 0000000..65e4556
--- /dev/null
+++ b/tests/lib/security/stringutils.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright (c) 2014 Lukas Reschke <lukas at 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

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list