Skip to content
This repository has been archived by the owner on Nov 25, 2020. It is now read-only.

Commit

Permalink
Encapsulate Crypto calls, add header to detect if it's a legacy or ne…
Browse files Browse the repository at this point in the history
…w encryption.
  • Loading branch information
cdujeu committed Sep 26, 2016
1 parent aaf0ec8 commit b745b30
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 92 deletions.
40 changes: 40 additions & 0 deletions core/src/core/src/phpunit/Pydio/Tests/Atomics/CryptoTests.php
@@ -0,0 +1,40 @@
<?php
/*
* Copyright 2007-2016 Abstrium <contact (at) pydio.com>
* This file is part of Pydio.
*
* Pydio is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pydio 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
* along with Pydio. If not, see <http://www.gnu.org/licenses/>.
*
* The latest code can be found at <https://pydio.com/>.
*/
namespace Pydio\Tests\Atomics;

use Pydio\Core\Utils\Crypto;

defined('AJXP_EXEC') or die('Access not allowed');

/**
* Class Crypto
* @package Pydio\Tests\Atomics
*/
class CryptoTests extends \PHPUnit_Framework_TestCase
{
public function testEncryptDecrypt(){
$key = "test";
$data = "toto";
$encoded = Crypto::encrypt($data, Crypto::buildKey($key, Crypto::getApplicationSecret()));
$decoded = Crypto::decrypt($encoded, Crypto::buildKey($key, Crypto::getApplicationSecret()));
$this->assertTrue($data === $decoded);
}
}
2 changes: 1 addition & 1 deletion core/src/core/src/pydio/Core/Controller/CliRunner.php
Expand Up @@ -76,7 +76,7 @@ public static function applyActionInBackground(ContextInterface $ctx, $actionNam
$logFile = $logDir . "/" . $token . ".out";

if (UsersService::usersEnabled()) {
$user = Crypto::encrypt($user, md5($token . Crypto::getCliSecret()));
$user = Crypto::encrypt($user, Crypto::buildKey($token , Crypto::getCliSecret()));
}
$robustInstallPath = str_replace("/", DIRECTORY_SEPARATOR, AJXP_INSTALL_PATH);
$cmd = ConfService::getGlobalConf("CLI_PHP") . " " . $robustInstallPath . DIRECTORY_SEPARATOR . "cmd.php -u=$user -t=$token -a=$actionName -r=$repositoryId";
Expand Down
46 changes: 2 additions & 44 deletions core/src/core/src/pydio/Core/Http/Cli/AuthCliMiddleware.php
Expand Up @@ -70,7 +70,8 @@ protected static function authenticateFromCliParameters($options){
} else {
// Consider "u" is a crypted version of u:p
$optToken = $options["t"];
$optUser = Crypto::decrypt($optUser, md5($optToken.Crypto::getCliSecret()));
$key = Crypto::buildKey($optToken, Crypto::getCliSecret());
$optUser = Crypto::decrypt($optUser, $key);
$envPass = MemorySafe::loadPasswordStringFromEnvironment($optUser);
if($envPass !== false){
unset($optToken);
Expand Down Expand Up @@ -111,49 +112,6 @@ public static function handleRequest(ServerRequestInterface $requestInterface, R
$optRepoId = $options["r"];

$impersonateUsers = false;
// TODO 1/ REIMPLEMENT parameter queue: to pass a file with many user names?
/**
* if (strpos($optUser, "queue:") === 0) {
$optUserQueue = substr($optUser, strlen("queue:"));
$optUser = false;
//echo("QUEUE : ".$optUserQueue);
if (is_file($optUserQueue)) {
$lines = file($optUserQueue);
if (count($lines) && !empty($lines[0])) {
$allUsers = explode(",", $lines[0]);
$optUser = array_shift($allUsers);
file_put_contents($optUserQueue, implode(",", $allUsers));
}
}
if ($optUser === false) {
if (is_file($optUserQueue)) {
unlink($optUserQueue);
}
die("No more users inside queue");
}
*/
// TODO 2/ REIMPLEMENT DETECT USER PARAMETER BASED ON REPOSITORY PATH OPTION ?
/*
if ($optDetectUser != false) {
$path = $repository->getOption("PATH", true);
if (strpos($path, "AJXP_USER") !== false) {
$path = str_replace(
array("AJXP_INSTALL_PATH", "AJXP_DATA_PATH", "/"),
array(AJXP_INSTALL_PATH, AJXP_DATA_PATH, DIRECTORY_SEPARATOR),
$path
);
$parts = explode("AJXP_USER", $path);
if(count($parts) == 1) $parts[1] = "";
$first = str_replace("\\", "\\\\", $parts[0]);
$last = str_replace("\\", "\\\\", $parts[1]);
*/
//if (preg_match("/$first(.*)$last.*/", $optDetectUser, $matches)) {
// $detectedUser = $matches[1];
// }
//}
//}


$loggedUser = self::authenticateFromCliParameters($options);

$requestInterface = $requestInterface->withAttribute("action", $options["a"]);
Expand Down
3 changes: 2 additions & 1 deletion core/src/core/src/pydio/Core/Http/Dav/AuthBackendDigest.php
Expand Up @@ -182,7 +182,8 @@ protected function updateCurrentUserRights($user)
*/
private function _decodePassword($encoded, $user)
{
return Crypto::decrypt($encoded, md5($user . $this->secretKey));
$key = Crypto::buildKey($user, Crypto::getApplicationSecret(), $encoded);
return Crypto::decrypt($encoded, $key);
}


Expand Down
78 changes: 69 additions & 9 deletions core/src/core/src/pydio/Core/Utils/Crypto.php
Expand Up @@ -22,6 +22,7 @@

use phpseclib\Crypt\Rijndael;
use Pydio\Core\Services\ConfService;
use Pydio\Core\Utils\Crypto\Key;
use Pydio\Core\Utils\Crypto\ZeroPaddingRijndael;
use Pydio\Core\Utils\Vars\StringHelper;

Expand All @@ -35,6 +36,7 @@
*/
class Crypto
{
const HEADER_CBC_128 = 'cbc-128';

/**
* @return string
Expand Down Expand Up @@ -62,32 +64,83 @@ public static function getCliSecret(){
* @param bool $base64encode
* @return string
*/
public static function getRandomSalt($base64encode = true){
public static function getRandomSalt($base64encode = true, $size = 32){
if(function_exists('openssl_random_pseudo_bytes')){
$salt = openssl_random_pseudo_bytes(32);
$salt = openssl_random_pseudo_bytes($size);
}else if (function_exists('mcrypt_create_iv')){
$salt = mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM);
}else{
$salt = StringHelper::generateRandomString(32, true);
$salt = StringHelper::generateRandomString($size, true);
}
return ($base64encode ? base64_encode($salt) : $salt);
}

/**
* @return string
*/
protected static function getDataHeader(){
return substr(md5(self::HEADER_CBC_128), 0, 16);
}

/**
* @param $data
* @return bool
*/
public static function hasCBCEnctypeHeader($data){
$h = self::getDataHeader();
return (strpos($data, $h) === 0);
}

/**
* @param $data
* @return bool
*/
private static function removeCBCEnctypeHeader(&$data){
$h = self::getDataHeader();
if(strpos($data, $h) === 0){
$data = substr($data, strlen($h));
return true;
}else{
return false;
}
}

/**
* Builds a key using various methods depending on legacy status or not
* @param string $userKey
* @param string $secret
* @param null $encodedData
* @return array|bool|string
*/
public static function buildKey($userKey, $secret, $encodedData = null){
if($encodedData === null){
// new encryption, use new method
return Key::create($userKey.$secret);
}else if(self::hasCBCEnctypeHeader($encodedData)){
// New method detected
return Key::create($userKey . $secret, Key::STRENGTH_MEDIUM);
}else{
// Legacy
return Key::createLegacy($userKey . $secret);
}
}

/**
* @param mixed $data
* @param string $key
* @param bool $base64encode
* @return mixed
*/
public static function encrypt($data, $key, $base64encode = true){
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
// Encrypt in new mode, prepending a fixed header to the encoded data.
$r = new ZeroPaddingRijndael(Rijndael::MODE_CBC);
$r->setKey($key);
$r->setBlockLength(256);
$r->setBlockLength(128);
$encoded = $r->encrypt($data);
if($base64encode) {
return base64_encode($encoded);
return self::getDataHeader().base64_encode($encoded);
} else {
return $encoded;
return self::getDataHeader().$encoded;
}
}

Expand All @@ -98,12 +151,19 @@ public static function encrypt($data, $key, $base64encode = true){
* @return mixed
*/
public static function decrypt($data, $key, $base64encoded = true){
$test = self::removeCBCEnctypeHeader($data);
if($base64encoded){
$data = base64_decode($data);
}
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
if($test){
$r = new ZeroPaddingRijndael(Rijndael::MODE_CBC);
$r->setBlockLength(128);
}else{
// Legacy encoding
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
$r->setBlockLength(256);
}
$r->setKey($key);
$r->setBlockLength(256);
return $r->decrypt($data);
}

Expand Down
101 changes: 101 additions & 0 deletions core/src/core/src/pydio/Core/Utils/Crypto/Key.php
@@ -0,0 +1,101 @@
<?php
/*
* Copyright 2007-2016 Abstrium <contact (at) pydio.com>
* This file is part of Pydio.
*
* Pydio is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pydio 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
* along with Pydio. If not, see <http://www.gnu.org/licenses/>.
*
* The latest code can be found at <https://pydio.com/>.
*/
namespace Pydio\Core\Utils\Crypto;

use Pydio\Core\Utils\Crypto;

defined('AJXP_EXEC') or die('Access not allowed');

/**
* Class Key
* @package Pydio\Core\Utils\Crypto
*/
class Key
{
const STRENGTH_LOW = 0;
const STRENGTH_MEDIUM = 1;
const STRENGTH_HIGH = 2;

const SIZE_128 = 16;
const SIZE_256 = 32;

/**
* @param $password
* @param int $strength
* @param null $options
* @return array|bool|string
*/
public static function create($password, $strength = Key::STRENGTH_LOW, $options = null){

if(!$options){
$options = array(
"strength" => self::STRENGTH_MEDIUM,
"size" => self::SIZE_256,
"iterations" => 20000,
"salt" => Crypto::getRandomSalt(self::SIZE_256),
"hash_function" => "SHA512"
);
}

if($strength == self::STRENGTH_HIGH && function_exists('openssl_random_pseudo_bytes')){

$aes_key = self::create($password);
$method = "aes-" . strlen($options["size"]) . "-cbc";

$key = openssl_random_pseudo_bytes($options["size"]);
$rsa = openssl_pkey_new(array(
"digest_algo" => "sha512",
"private_key_bits" => "4096",
"private_key_type" => OPENSSL_KEYTYPE_RSA
));
openssl_pkey_export($rsa, $private);

$iv = openssl_random_pseudo_bytes(16);
$private = openssl_encrypt($private, $method, $aes_key, OPENSSL_RAW_DATA, $iv);
$public = openssl_pkey_get_details($rsa)["key"];

$options["public"] = $public;
$options["private"] = $private;
$options["iv"] = $iv;
openssl_public_encrypt($key, $options["key"], $public);

return array(
$key,
$options
);

} else if($strength == self::STRENGTH_LOW){
return substr(hash($options["hash_function"], $password), 0, $options["size"]);

} else {
return openssl_pbkdf2($password, $options["salt"], $options["size"], $options["iterations"], $options["hash_function"]);
}
}

/**
* @param $password
* @return string
*/
public static function createLegacy($password){
return md5($password);
}

}

0 comments on commit b745b30

Please sign in to comment.