This adds some security utilities to core including: - A library for basic crypto operations (e.g. to encrypt passwords) - A better library for cryptographic actions which allows you to specify the charset - A library for secure string comparisions Remove .htaccess Remove .htaccess Fix typo Add public API Use timing constant comparision Remove CBC constant Adjust code Remove confusing $thisremotes/origin/fix-10825
parent
3115053bbb
commit
d26a9c3c58
@ -0,0 +1,104 @@ |
||||
<?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; |
||||
|
||||
/** |
||||
* 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; |
||||
|
||||
function __construct() { |
||||
$this->cipher = new Crypt_AES(); |
||||
} |
||||
|
||||
/** |
||||
* @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 = \OC::$server->getConfig()->getSystemValue('secret'); |
||||
} |
||||
|
||||
$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 = \OC::$server->getConfig()->getSystemValue('secret'); |
||||
} |
||||
$this->cipher->setPassword($password); |
||||
|
||||
$iv = \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate($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 = \OC::$server->getConfig()->getSystemValue('secret'); |
||||
} |
||||
$this->cipher->setPassword($password); |
||||
|
||||
$parts = explode('|', $authenticatedCiphertext); |
||||
if(sizeof($parts) !== 3) { |
||||
throw new \Exception('Authenticated ciphertext could not be decoded.'); |
||||
} |
||||
|
||||
$ciphertext = hex2bin($parts[0]); |
||||
$iv = $parts[1]; |
||||
$hmac = hex2bin($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); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,79 @@ |
||||
<?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 RandomLib; |
||||
use Sabre\DAV\Exception; |
||||
use OCP\Security\ISecureRandom; |
||||
|
||||
/** |
||||
* Class SecureRandom provides a layer around RandomLib to generate |
||||
* secure random strings. |
||||
* |
||||
* Usage: |
||||
* \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(10); |
||||
* |
||||
* @package OC\Security |
||||
*/ |
||||
class SecureRandom implements ISecureRandom { |
||||
|
||||
/** @var \RandomLib\Factory */ |
||||
var $factory; |
||||
/** @var \RandomLib\Generator */ |
||||
var $generator; |
||||
|
||||
function __construct() { |
||||
$this->factory = new RandomLib\Factory; |
||||
} |
||||
|
||||
/** |
||||
* Convenience method to get a low strength random number generator. |
||||
* |
||||
* Low Strength should be used anywhere that random strings are needed |
||||
* in a non-cryptographical setting. They are not strong enough to be |
||||
* used as keys or salts. They are however useful for one-time use tokens. |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function getLowStrengthGenerator() { |
||||
$this->generator = $this->factory->getLowStrengthGenerator(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Convenience method to get a medium strength random number generator. |
||||
* |
||||
* Medium Strength should be used for most needs of a cryptographic nature. |
||||
* They are strong enough to be used as keys and salts. However, they do |
||||
* take some time and resources to generate, so they should not be over-used |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function getMediumStrengthGenerator() { |
||||
$this->generator = $this->factory->getMediumStrengthGenerator(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Generate a random string of specified length. |
||||
* @param string $length The length of the generated string |
||||
* @param string $characters An optional list of characters to use if no characterlist is |
||||
* specified 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./ |
||||
* is used. |
||||
* @return string |
||||
* @throws \Exception If the generator is not initialized. |
||||
*/ |
||||
public function generate($length, $characters = '') { |
||||
if(is_null($this->generator)) { |
||||
throw new \Exception('Generator is not initialized.'); |
||||
} |
||||
|
||||
return $this->generator->generateString($length, $characters); |
||||
} |
||||
} |
@ -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. |
||||
*/ |
||||
|
||||
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. |
||||
* @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(function_exists('hash_equals')) { |
||||
return hash_equals($expected, $input); |
||||
} |
||||
|
||||
$randomString = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(10); |
||||
|
||||
if(hash('sha512', $expected.$randomString) === hash('sha512', $input.$randomString)) { |
||||
if($expected === $input) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
@ -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 = ''); |
||||
} |
@ -0,0 +1,53 @@ |
||||
<?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 SecureRandom provides a layer around RandomLib to generate |
||||
* secure random numbers. |
||||
* |
||||
* Usage: |
||||
* $rng = new \OC\Security\SecureRandom(); |
||||
* $randomString = $rng->getMediumStrengthGenerator()->generateString(30); |
||||
* |
||||
* @package OCP\Security |
||||
*/ |
||||
interface ISecureRandom { |
||||
|
||||
/** |
||||
* Convenience method to get a low strength random number generator. |
||||
* |
||||
* Low Strength should be used anywhere that random strings are needed |
||||
* in a non-cryptographical setting. They are not strong enough to be |
||||
* used as keys or salts. They are however useful for one-time use tokens. |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function getLowStrengthGenerator(); |
||||
|
||||
/** |
||||
* Convenience method to get a medium strength random number generator. |
||||
* |
||||
* Medium Strength should be used for most needs of a cryptographic nature. |
||||
* They are strong enough to be used as keys and salts. However, they do |
||||
* take some time and resources to generate, so they should not be over-used |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function getMediumStrengthGenerator(); |
||||
|
||||
/** |
||||
* Generate a random string of specified length. |
||||
* @param string $length The length of the generated string |
||||
* @param string $characters An optional list of characters to use |
||||
* @return string |
||||
* @throws \Exception |
||||
*/ |
||||
public function generate($length, $characters = ''); |
||||
} |
@ -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); |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
<?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\Repair; |
||||
|
||||
use OC\Hooks\BasicEmitter; |
||||
use OC\RepairStep; |
||||
use Sabre\DAV\Exception; |
||||
|
||||
class RepairConfig extends BasicEmitter implements RepairStep { |
||||
|
||||
public function getName() { |
||||
return 'Repair config'; |
||||
} |
||||
|
||||
/** |
||||
* Updates the configuration after running an update |
||||
*/ |
||||
public function run() { |
||||
$this->addSecret(); |
||||
} |
||||
|
||||
/** |
||||
* Adds a secret to config.php |
||||
*/ |
||||
private function addSecret() { |
||||
if(\OC::$server->getConfig()->getSystemValue('secret', null) === null) { |
||||
$secret = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(48); |
||||
\OC::$server->getConfig()->setSystemValue('secret', $secret); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,63 @@ |
||||
<?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 { |
||||
|
||||
function testDefaultEncrypt() { |
||||
$stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'; |
||||
$crypto = new Crypto(); |
||||
$ciphertext = $crypto->encrypt($stringToEncrypt); |
||||
$this->assertEquals($stringToEncrypt, $crypto->decrypt($ciphertext)); |
||||
|
||||
$stringToEncrypt = ''; |
||||
$ciphertext = $crypto->encrypt($stringToEncrypt); |
||||
$this->assertEquals($stringToEncrypt, $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.'; |
||||
$encryptCrypto = new Crypto(); |
||||
$ciphertext = $encryptCrypto->encrypt($stringToEncrypt); |
||||
|
||||
$decryptCrypto = new Crypto(); |
||||
$this->assertFalse($decryptCrypto->decrypt($ciphertext, 'A wrong password!')); |
||||
} |
||||
|
||||
function testLaterDecryption() { |
||||
$stringToEncrypt = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt.'; |
||||
$encryptedString = '560f5436ba864b9f12f7f7ca6d41c327554a6f2c0a160a03316b202af07c65163274993f3a46e7547c07ba89304f00594a2f3bd99f83859097c58049c39d0d4ade10e0de914ff0604961e7c849d0271ed6c0b23f984ba16e7d033e3305fb0910e7b6a2a65c988d17dbee71d8f953684d|d2kdFUspVjC0Y0sr|1a5feacf87eaa6869a6abdfba9a296e7bbad45b6ad89f7dce67cdc98e2da5dc4379cc672cc655e52bbf19599bf59482fbea13a73937697fa656bf10f3fc4f1aa'; |
||||
$crypto = new Crypto(); |
||||
$this->assertEquals($stringToEncrypt, $crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd')); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException \Exception |
||||
* @expectedExceptionMessage HMAC does not match. |
||||
*/ |
||||
function testWrongIV() { |
||||
$encryptedString = '560f5436ba864b9f12f7f7ca6d41c327554a6f2c0a160a03316b202af07c65163274993f3a46e7547c07ba89304f00594a2f3bd99f83859097c58049c39d0d4ade10e0de914ff0604961e7c849d0271ed6c0b23f984ba16e7d033e3305fb0910e7b6a2a65c988d17dbee71d8f953684d|d2kdFUspVjC0o0sr|1a5feacf87eaa6869a6abdfba9a296e7bbad45b6ad89f7dce67cdc98e2da5dc4379cc672cc655e52bbf19599bf59482fbea13a73937697fa656bf10f3fc4f1aa'; |
||||
$crypto = new Crypto(); |
||||
$crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd'); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException \Exception |
||||
* @expectedExceptionMessage Authenticated ciphertext could not be decoded. |
||||
*/ |
||||
function testWrongParameters() { |
||||
$encryptedString = '1|2'; |
||||
$crypto = new Crypto(); |
||||
$crypto->decrypt($encryptedString, 'ThisIsAVeryS3cur3P4ssw0rd'); |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
<?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. |
||||
*/ |
||||
|
||||
class SecureRandomTest extends \PHPUnit_Framework_TestCase { |
||||
|
||||
public function stringGenerationProvider() { |
||||
return array( |
||||
array(0, 0), |
||||
array(1, 1), |
||||
array(128, 128), |
||||
array(256, 256), |
||||
array(1024, 1024), |
||||
array(2048, 2048), |
||||
array(64000, 64000), |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider stringGenerationProvider |
||||
*/ |
||||
function testGetLowStrengthGeneratorLength($length, $expectedLength) { |
||||
$rng = new \OC\Security\SecureRandom(); |
||||
$generator = $rng->getLowStrengthGenerator(); |
||||
|
||||
$this->assertEquals($expectedLength, strlen($generator->generate($length))); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider stringGenerationProvider |
||||
*/ |
||||
function testMediumLowStrengthGeneratorLength($length, $expectedLength) { |
||||
$rng = new \OC\Security\SecureRandom(); |
||||
$generator = $rng->getMediumStrengthGenerator(); |
||||
|
||||
$this->assertEquals($expectedLength, strlen($generator->generate($length))); |
||||
} |
||||
|
||||
/** |
||||
* @expectedException \Exception |
||||
* @expectedExceptionMessage Generator is not initialized |
||||
*/ |
||||
function testUninitializedGenerate() { |
||||
$rng = new \OC\Security\SecureRandom(); |
||||
$rng->generate(30); |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
<?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 { |
||||
|
||||
function testEquals() { |
||||
$this->assertTrue(StringUtils::equals('GpKY9fSnWRaeFNJbES99zVGvA', 'GpKY9fSnWRaeFNJbES99zVGvA')); |
||||
$this->assertFalse(StringUtils::equals('GpKY9fSnWNJbES99zVGvA', 'GpKY9fSnWRaeFNJbES99zVGvA')); |
||||
$this->assertFalse(StringUtils::equals('', 'GpKY9fSnWRaeFNJbES99zVGvA')); |
||||
$this->assertFalse(StringUtils::equals('GpKY9fSnWRaeFNJbES99zVGvA', '')); |
||||
$this->assertTrue(StringUtils::equals('', '')); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue