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

Commit b745b30

Browse files
committed
Encapsulate Crypto calls, add header to detect if it's a legacy or new encryption.
1 parent aaf0ec8 commit b745b30

File tree

13 files changed

+291
-92
lines changed

13 files changed

+291
-92
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/*
3+
* Copyright 2007-2016 Abstrium <contact (at) pydio.com>
4+
* This file is part of Pydio.
5+
*
6+
* Pydio is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Pydio is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with Pydio. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* The latest code can be found at <https://pydio.com/>.
20+
*/
21+
namespace Pydio\Tests\Atomics;
22+
23+
use Pydio\Core\Utils\Crypto;
24+
25+
defined('AJXP_EXEC') or die('Access not allowed');
26+
27+
/**
28+
* Class Crypto
29+
* @package Pydio\Tests\Atomics
30+
*/
31+
class CryptoTests extends \PHPUnit_Framework_TestCase
32+
{
33+
public function testEncryptDecrypt(){
34+
$key = "test";
35+
$data = "toto";
36+
$encoded = Crypto::encrypt($data, Crypto::buildKey($key, Crypto::getApplicationSecret()));
37+
$decoded = Crypto::decrypt($encoded, Crypto::buildKey($key, Crypto::getApplicationSecret()));
38+
$this->assertTrue($data === $decoded);
39+
}
40+
}

core/src/core/src/pydio/Core/Controller/CliRunner.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static function applyActionInBackground(ContextInterface $ctx, $actionNam
7676
$logFile = $logDir . "/" . $token . ".out";
7777

7878
if (UsersService::usersEnabled()) {
79-
$user = Crypto::encrypt($user, md5($token . Crypto::getCliSecret()));
79+
$user = Crypto::encrypt($user, Crypto::buildKey($token , Crypto::getCliSecret()));
8080
}
8181
$robustInstallPath = str_replace("/", DIRECTORY_SEPARATOR, AJXP_INSTALL_PATH);
8282
$cmd = ConfService::getGlobalConf("CLI_PHP") . " " . $robustInstallPath . DIRECTORY_SEPARATOR . "cmd.php -u=$user -t=$token -a=$actionName -r=$repositoryId";

core/src/core/src/pydio/Core/Http/Cli/AuthCliMiddleware.php

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ protected static function authenticateFromCliParameters($options){
7070
} else {
7171
// Consider "u" is a crypted version of u:p
7272
$optToken = $options["t"];
73-
$optUser = Crypto::decrypt($optUser, md5($optToken.Crypto::getCliSecret()));
73+
$key = Crypto::buildKey($optToken, Crypto::getCliSecret());
74+
$optUser = Crypto::decrypt($optUser, $key);
7475
$envPass = MemorySafe::loadPasswordStringFromEnvironment($optUser);
7576
if($envPass !== false){
7677
unset($optToken);
@@ -111,49 +112,6 @@ public static function handleRequest(ServerRequestInterface $requestInterface, R
111112
$optRepoId = $options["r"];
112113

113114
$impersonateUsers = false;
114-
// TODO 1/ REIMPLEMENT parameter queue: to pass a file with many user names?
115-
/**
116-
* if (strpos($optUser, "queue:") === 0) {
117-
$optUserQueue = substr($optUser, strlen("queue:"));
118-
$optUser = false;
119-
//echo("QUEUE : ".$optUserQueue);
120-
if (is_file($optUserQueue)) {
121-
$lines = file($optUserQueue);
122-
if (count($lines) && !empty($lines[0])) {
123-
$allUsers = explode(",", $lines[0]);
124-
$optUser = array_shift($allUsers);
125-
file_put_contents($optUserQueue, implode(",", $allUsers));
126-
}
127-
}
128-
if ($optUser === false) {
129-
if (is_file($optUserQueue)) {
130-
unlink($optUserQueue);
131-
}
132-
die("No more users inside queue");
133-
}
134-
*/
135-
// TODO 2/ REIMPLEMENT DETECT USER PARAMETER BASED ON REPOSITORY PATH OPTION ?
136-
/*
137-
if ($optDetectUser != false) {
138-
$path = $repository->getOption("PATH", true);
139-
if (strpos($path, "AJXP_USER") !== false) {
140-
$path = str_replace(
141-
array("AJXP_INSTALL_PATH", "AJXP_DATA_PATH", "/"),
142-
array(AJXP_INSTALL_PATH, AJXP_DATA_PATH, DIRECTORY_SEPARATOR),
143-
$path
144-
);
145-
$parts = explode("AJXP_USER", $path);
146-
if(count($parts) == 1) $parts[1] = "";
147-
$first = str_replace("\\", "\\\\", $parts[0]);
148-
$last = str_replace("\\", "\\\\", $parts[1]);
149-
*/
150-
//if (preg_match("/$first(.*)$last.*/", $optDetectUser, $matches)) {
151-
// $detectedUser = $matches[1];
152-
// }
153-
//}
154-
//}
155-
156-
157115
$loggedUser = self::authenticateFromCliParameters($options);
158116

159117
$requestInterface = $requestInterface->withAttribute("action", $options["a"]);

core/src/core/src/pydio/Core/Http/Dav/AuthBackendDigest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ protected function updateCurrentUserRights($user)
182182
*/
183183
private function _decodePassword($encoded, $user)
184184
{
185-
return Crypto::decrypt($encoded, md5($user . $this->secretKey));
185+
$key = Crypto::buildKey($user, Crypto::getApplicationSecret(), $encoded);
186+
return Crypto::decrypt($encoded, $key);
186187
}
187188

188189

core/src/core/src/pydio/Core/Utils/Crypto.php

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
use phpseclib\Crypt\Rijndael;
2424
use Pydio\Core\Services\ConfService;
25+
use Pydio\Core\Utils\Crypto\Key;
2526
use Pydio\Core\Utils\Crypto\ZeroPaddingRijndael;
2627
use Pydio\Core\Utils\Vars\StringHelper;
2728

@@ -35,6 +36,7 @@
3536
*/
3637
class Crypto
3738
{
39+
const HEADER_CBC_128 = 'cbc-128';
3840

3941
/**
4042
* @return string
@@ -62,32 +64,83 @@ public static function getCliSecret(){
6264
* @param bool $base64encode
6365
* @return string
6466
*/
65-
public static function getRandomSalt($base64encode = true){
67+
public static function getRandomSalt($base64encode = true, $size = 32){
6668
if(function_exists('openssl_random_pseudo_bytes')){
67-
$salt = openssl_random_pseudo_bytes(32);
69+
$salt = openssl_random_pseudo_bytes($size);
6870
}else if (function_exists('mcrypt_create_iv')){
6971
$salt = mcrypt_create_iv(PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM);
7072
}else{
71-
$salt = StringHelper::generateRandomString(32, true);
73+
$salt = StringHelper::generateRandomString($size, true);
7274
}
7375
return ($base64encode ? base64_encode($salt) : $salt);
7476
}
7577

78+
/**
79+
* @return string
80+
*/
81+
protected static function getDataHeader(){
82+
return substr(md5(self::HEADER_CBC_128), 0, 16);
83+
}
84+
85+
/**
86+
* @param $data
87+
* @return bool
88+
*/
89+
public static function hasCBCEnctypeHeader($data){
90+
$h = self::getDataHeader();
91+
return (strpos($data, $h) === 0);
92+
}
93+
94+
/**
95+
* @param $data
96+
* @return bool
97+
*/
98+
private static function removeCBCEnctypeHeader(&$data){
99+
$h = self::getDataHeader();
100+
if(strpos($data, $h) === 0){
101+
$data = substr($data, strlen($h));
102+
return true;
103+
}else{
104+
return false;
105+
}
106+
}
107+
108+
/**
109+
* Builds a key using various methods depending on legacy status or not
110+
* @param string $userKey
111+
* @param string $secret
112+
* @param null $encodedData
113+
* @return array|bool|string
114+
*/
115+
public static function buildKey($userKey, $secret, $encodedData = null){
116+
if($encodedData === null){
117+
// new encryption, use new method
118+
return Key::create($userKey.$secret);
119+
}else if(self::hasCBCEnctypeHeader($encodedData)){
120+
// New method detected
121+
return Key::create($userKey . $secret, Key::STRENGTH_MEDIUM);
122+
}else{
123+
// Legacy
124+
return Key::createLegacy($userKey . $secret);
125+
}
126+
}
127+
76128
/**
77129
* @param mixed $data
78130
* @param string $key
79131
* @param bool $base64encode
80132
* @return mixed
81133
*/
82134
public static function encrypt($data, $key, $base64encode = true){
83-
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
135+
// Encrypt in new mode, prepending a fixed header to the encoded data.
136+
$r = new ZeroPaddingRijndael(Rijndael::MODE_CBC);
84137
$r->setKey($key);
85-
$r->setBlockLength(256);
138+
$r->setBlockLength(128);
86139
$encoded = $r->encrypt($data);
87140
if($base64encode) {
88-
return base64_encode($encoded);
141+
return self::getDataHeader().base64_encode($encoded);
89142
} else {
90-
return $encoded;
143+
return self::getDataHeader().$encoded;
91144
}
92145
}
93146

@@ -98,12 +151,19 @@ public static function encrypt($data, $key, $base64encode = true){
98151
* @return mixed
99152
*/
100153
public static function decrypt($data, $key, $base64encoded = true){
154+
$test = self::removeCBCEnctypeHeader($data);
101155
if($base64encoded){
102156
$data = base64_decode($data);
103157
}
104-
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
158+
if($test){
159+
$r = new ZeroPaddingRijndael(Rijndael::MODE_CBC);
160+
$r->setBlockLength(128);
161+
}else{
162+
// Legacy encoding
163+
$r = new ZeroPaddingRijndael(Rijndael::MODE_ECB);
164+
$r->setBlockLength(256);
165+
}
105166
$r->setKey($key);
106-
$r->setBlockLength(256);
107167
return $r->decrypt($data);
108168
}
109169

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
/*
3+
* Copyright 2007-2016 Abstrium <contact (at) pydio.com>
4+
* This file is part of Pydio.
5+
*
6+
* Pydio is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* Pydio is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with Pydio. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* The latest code can be found at <https://pydio.com/>.
20+
*/
21+
namespace Pydio\Core\Utils\Crypto;
22+
23+
use Pydio\Core\Utils\Crypto;
24+
25+
defined('AJXP_EXEC') or die('Access not allowed');
26+
27+
/**
28+
* Class Key
29+
* @package Pydio\Core\Utils\Crypto
30+
*/
31+
class Key
32+
{
33+
const STRENGTH_LOW = 0;
34+
const STRENGTH_MEDIUM = 1;
35+
const STRENGTH_HIGH = 2;
36+
37+
const SIZE_128 = 16;
38+
const SIZE_256 = 32;
39+
40+
/**
41+
* @param $password
42+
* @param int $strength
43+
* @param null $options
44+
* @return array|bool|string
45+
*/
46+
public static function create($password, $strength = Key::STRENGTH_LOW, $options = null){
47+
48+
if(!$options){
49+
$options = array(
50+
"strength" => self::STRENGTH_MEDIUM,
51+
"size" => self::SIZE_256,
52+
"iterations" => 20000,
53+
"salt" => Crypto::getRandomSalt(self::SIZE_256),
54+
"hash_function" => "SHA512"
55+
);
56+
}
57+
58+
if($strength == self::STRENGTH_HIGH && function_exists('openssl_random_pseudo_bytes')){
59+
60+
$aes_key = self::create($password);
61+
$method = "aes-" . strlen($options["size"]) . "-cbc";
62+
63+
$key = openssl_random_pseudo_bytes($options["size"]);
64+
$rsa = openssl_pkey_new(array(
65+
"digest_algo" => "sha512",
66+
"private_key_bits" => "4096",
67+
"private_key_type" => OPENSSL_KEYTYPE_RSA
68+
));
69+
openssl_pkey_export($rsa, $private);
70+
71+
$iv = openssl_random_pseudo_bytes(16);
72+
$private = openssl_encrypt($private, $method, $aes_key, OPENSSL_RAW_DATA, $iv);
73+
$public = openssl_pkey_get_details($rsa)["key"];
74+
75+
$options["public"] = $public;
76+
$options["private"] = $private;
77+
$options["iv"] = $iv;
78+
openssl_public_encrypt($key, $options["key"], $public);
79+
80+
return array(
81+
$key,
82+
$options
83+
);
84+
85+
} else if($strength == self::STRENGTH_LOW){
86+
return substr(hash($options["hash_function"], $password), 0, $options["size"]);
87+
88+
} else {
89+
return openssl_pbkdf2($password, $options["salt"], $options["size"], $options["iterations"], $options["hash_function"]);
90+
}
91+
}
92+
93+
/**
94+
* @param $password
95+
* @return string
96+
*/
97+
public static function createLegacy($password){
98+
return md5($password);
99+
}
100+
101+
}

0 commit comments

Comments
 (0)