mirror of
https://github.com/nextcloud/passman.git
synced 2025-11-09 13:44:40 +08:00
Adjust reviewed code
This commit is contained in:
parent
3f4afba34f
commit
5f7661659a
8 changed files with 127 additions and 53 deletions
|
|
@ -51,6 +51,15 @@ Untested databases:
|
||||||
|
|
||||||
For more screenshots: [Click here](http://imgur.com/a/giKVt)
|
For more screenshots: [Click here](http://imgur.com/a/giKVt)
|
||||||
|
|
||||||
|
## Encryption (server side)
|
||||||
|
All passwords are encrypted client side AND server side.
|
||||||
|
This means that if you move to another server you have to backup the following from config.php
|
||||||
|
- `passwordsalt`
|
||||||
|
- `secret`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Code reviews
|
## Code reviews
|
||||||
If you have any improvements regarding our code.
|
If you have any improvements regarding our code.
|
||||||
Please do the following
|
Please do the following
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,11 @@ class CredentialController extends ApiController {
|
||||||
$storedCredential->setSharedKey('');
|
$storedCredential->setSharedKey('');
|
||||||
$credential['shared_key'] = '';
|
$credential['shared_key'] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!isset($credential['shared_key'])){
|
||||||
|
$credential['shared_key'] = $storedCredential->getSharedKey();
|
||||||
|
}
|
||||||
|
|
||||||
if (!$skip_revision) {
|
if (!$skip_revision) {
|
||||||
$this->credentialRevisionService->createRevision($storedCredential, $storedCredential->getUserId(), $credential_id, $this->userId);
|
$this->credentialRevisionService->createRevision($storedCredential, $storedCredential->getUserId(), $credential_id, $this->userId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class CredentialRevision extends Entity implements \JsonSerializable {
|
||||||
protected $credentialId;
|
protected $credentialId;
|
||||||
protected $userId;
|
protected $userId;
|
||||||
protected $created;
|
protected $created;
|
||||||
public $credentialData;
|
protected $credentialData;
|
||||||
protected $editedBy;
|
protected $editedBy;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,8 @@ class CredentialService {
|
||||||
$acl = $this->sharingACL->getItemACL($user_id, $credential->getGuid());
|
$acl = $this->sharingACL->getItemACL($user_id, $credential->getGuid());
|
||||||
if ($acl->hasPermission(SharingACL::READ)) {
|
if ($acl->hasPermission(SharingACL::READ)) {
|
||||||
return $this->encryptService->decryptCredential($credential);
|
return $this->encryptService->decryptCredential($credential);
|
||||||
|
} else {
|
||||||
|
throw new DoesNotExistException("Did expect one result but found none when executing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,21 @@ use Icewind\SMB\Exception\Exception;
|
||||||
use OCA\Passman\Db\Credential;
|
use OCA\Passman\Db\Credential;
|
||||||
use OCA\Passman\Db\File;
|
use OCA\Passman\Db\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to handle secure encryption and decryption of arbitrary data
|
||||||
|
*
|
||||||
|
* Note that this is not just straight encryption. It also has a few other
|
||||||
|
* features in it to make the encrypted data far more secure. Note that any
|
||||||
|
* other implementations used to decrypt data will have to do the same exact
|
||||||
|
* operations.
|
||||||
|
*
|
||||||
|
* Security Benefits:
|
||||||
|
*
|
||||||
|
* - Uses Key stretching
|
||||||
|
* - Hides the Initialization Vector
|
||||||
|
* - Does HMAC verification of source data
|
||||||
|
*
|
||||||
|
*/
|
||||||
class EncryptService {
|
class EncryptService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,7 +54,7 @@ class EncryptService {
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $supportedAlgos = array(
|
const SUPPORTED_ALGORITHMS = array(
|
||||||
'aes-256-cbc' => array('name' => 'AES-256', 'keySize' => 32, 'blockSize' => 32),
|
'aes-256-cbc' => array('name' => 'AES-256', 'keySize' => 32, 'blockSize' => 32),
|
||||||
'bf' => array('name' => 'BF', 'keySize' => 16, 'blockSize' => 8),
|
'bf' => array('name' => 'BF', 'keySize' => 16, 'blockSize' => 8),
|
||||||
'des' => array('name' => 'DES', 'keySize' => 7, 'blockSize' => 8),
|
'des' => array('name' => 'DES', 'keySize' => 7, 'blockSize' => 8),
|
||||||
|
|
@ -47,37 +62,16 @@ class EncryptService {
|
||||||
'cast5' => array('name' => 'CAST5', 'keySize' => 16, 'blockSize' => 8),
|
'cast5' => array('name' => 'CAST5', 'keySize' => 16, 'blockSize' => 8),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
const OP_ENCRYPT = 'encrypt';
|
||||||
* Supported encryption modes
|
const OP_DECRYPT = 'decrypt';
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $supportedModes = array(
|
|
||||||
'cbc' => 'CBC',
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// The fields of a credential which are encrypted
|
||||||
public $encrypted_credential_fields = array(
|
public $encrypted_credential_fields = array(
|
||||||
'description', 'username', 'password', 'files', 'custom_fields', 'otp', 'email', 'tags', 'url'
|
'description', 'username', 'password', 'files', 'custom_fields', 'otp', 'email', 'tags', 'url'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Contains the server key
|
||||||
private $server_key;
|
private $server_key;
|
||||||
/**
|
|
||||||
* A class to handle secure encryption and decryption of arbitrary data
|
|
||||||
*
|
|
||||||
* Note that this is not just straight encryption. It also has a few other
|
|
||||||
* features in it to make the encrypted data far more secure. Note that any
|
|
||||||
* other implementations used to decrypt data will have to do the same exact
|
|
||||||
* operations.
|
|
||||||
*
|
|
||||||
* Security Benefits:
|
|
||||||
*
|
|
||||||
* - Uses Key stretching
|
|
||||||
* - Hides the Initialization Vector
|
|
||||||
* - Does HMAC verification of source data
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string $cipher The openssl cipher to use for this instance
|
* @var string $cipher The openssl cipher to use for this instance
|
||||||
|
|
@ -95,9 +89,11 @@ class EncryptService {
|
||||||
* @param SettingsService $settings
|
* @param SettingsService $settings
|
||||||
*/
|
*/
|
||||||
public function __construct(SettingsService $settings) {
|
public function __construct(SettingsService $settings) {
|
||||||
$this->cipher = $settings->getAppSetting('server_side_encryption');
|
$this->cipher = $settings->getAppSetting('server_side_encryption', 'aes-256-cbc');
|
||||||
$this->rounds = (int)100;
|
$password_salt = \OC::$server->getConfig()->getSystemValue('passwordsalt', '');
|
||||||
$this->server_key = \OC::$server->getConfig()->getSystemValue('passwordsalt', '');
|
$secret = \OC::$server->getConfig()->getSystemValue('secret', '');
|
||||||
|
$this->server_key = $password_salt . $secret;
|
||||||
|
$this->rounds = $settings->getAppSetting('rounds_pbkdf2_stretching', 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,7 +117,7 @@ class EncryptService {
|
||||||
* @return int Value is in bytes
|
* @return int Value is in bytes
|
||||||
*/
|
*/
|
||||||
public function getKeySize() {
|
public function getKeySize() {
|
||||||
return $this->supportedAlgos[$this->cipher]['keySize'];
|
return EncryptService::SUPPORTED_ALGORITHMS[$this->cipher]['keySize'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,7 +151,7 @@ class EncryptService {
|
||||||
|
|
||||||
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
|
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
|
||||||
|
|
||||||
if (!EncryptService::hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
|
if (!$this->hash_equals(hash_hmac('sha512', $enc, $macKey, true), $mac)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,9 +170,13 @@ class EncryptService {
|
||||||
* @returns string The encrypted data
|
* @returns string The encrypted data
|
||||||
*/
|
*/
|
||||||
public function encrypt($data, $key) {
|
public function encrypt($data, $key) {
|
||||||
$salt = openssl_random_pseudo_bytes(128);
|
if (function_exists('random_bytes')) {
|
||||||
list ($cipherKey, $macKey, $iv) = EncryptService::getKeys($salt, $key);
|
$salt = random_bytes(128);
|
||||||
$data = EncryptService::pad($data);
|
} else {
|
||||||
|
$salt = openssl_random_pseudo_bytes(128);
|
||||||
|
}
|
||||||
|
list ($cipherKey, $macKey, $iv) = $this->getKeys($salt, $key);
|
||||||
|
$data = $this->pad($data);
|
||||||
$enc = openssl_encrypt($data, $this->cipher, $cipherKey, true, $iv);
|
$enc = openssl_encrypt($data, $this->cipher, $cipherKey, true, $iv);
|
||||||
$mac = hash_hmac('sha512', $enc, $macKey, true);
|
$mac = hash_hmac('sha512', $enc, $macKey, true);
|
||||||
$data = bin2hex($salt . $enc . $mac);
|
$data = bin2hex($salt . $enc . $mac);
|
||||||
|
|
@ -197,7 +197,7 @@ class EncryptService {
|
||||||
$keySize = openssl_cipher_iv_length($this->cipher);
|
$keySize = openssl_cipher_iv_length($this->cipher);
|
||||||
$length = 2 * $keySize + $ivSize;
|
$length = 2 * $keySize + $ivSize;
|
||||||
|
|
||||||
$key = EncryptService::pbkdf2('sha512', $key, $salt, $this->rounds, $length);
|
$key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
|
||||||
|
|
||||||
$cipherKey = substr($key, 0, $keySize);
|
$cipherKey = substr($key, 0, $keySize);
|
||||||
$macKey = substr($key, $keySize, $keySize);
|
$macKey = substr($key, $keySize, $keySize);
|
||||||
|
|
@ -206,7 +206,11 @@ class EncryptService {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hash_equals($a, $b) {
|
protected function hash_equals($a, $b) {
|
||||||
$key = openssl_random_pseudo_bytes(128);
|
if (function_exists('random_bytes')) {
|
||||||
|
$key = random_bytes(128);
|
||||||
|
} else {
|
||||||
|
$key = openssl_random_pseudo_bytes(128);
|
||||||
|
}
|
||||||
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
|
return hash_hmac('sha512', $a, $key) === hash_hmac('sha512', $b, $key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,7 +283,7 @@ class EncryptService {
|
||||||
* @return Credential|array
|
* @return Credential|array
|
||||||
*/
|
*/
|
||||||
public function decryptCredential($credential) {
|
public function decryptCredential($credential) {
|
||||||
return $this->handleCredential($credential, 'decrypt');
|
return $this->handleCredential($credential, EncryptService::OP_DECRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -290,12 +294,11 @@ class EncryptService {
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function encryptCredential($credential) {
|
public function encryptCredential($credential) {
|
||||||
return $this->handleCredential($credential, 'encrypt');
|
return $this->handleCredential($credential, EncryptService::OP_ENCRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function extractKeysFromCredential($credential) {
|
||||||
private function extractKeysFromCredential($credential){
|
|
||||||
$userKey = '';
|
$userKey = '';
|
||||||
$userSuppliedKey = '';
|
$userSuppliedKey = '';
|
||||||
if ($credential instanceof Credential) {
|
if ($credential instanceof Credential) {
|
||||||
|
|
@ -317,12 +320,10 @@ class EncryptService {
|
||||||
* @return Credential|array
|
* @return Credential|array
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function handleCredential($credential, $op) {
|
private function handleCredential($credential, $service_function) {
|
||||||
$service_function = ($op === 'encrypt') ? 'encrypt' : 'decrypt';
|
|
||||||
|
|
||||||
list($userKey, $userSuppliedKey) = $this->extractKeysFromCredential($credential);
|
list($userKey, $userSuppliedKey) = $this->extractKeysFromCredential($credential);
|
||||||
|
|
||||||
$key = EncryptService::makeKey($userKey, $this->server_key, $userSuppliedKey);
|
$key = $this->makeKey($userKey, $this->server_key, $userSuppliedKey);
|
||||||
foreach ($this->encrypted_credential_fields as $field) {
|
foreach ($this->encrypted_credential_fields as $field) {
|
||||||
if ($credential instanceof Credential) {
|
if ($credential instanceof Credential) {
|
||||||
$field = str_replace(' ', '', str_replace('_', ' ', ucwords($field, '_')));
|
$field = str_replace(' ', '', str_replace('_', ' ', ucwords($field, '_')));
|
||||||
|
|
@ -346,7 +347,7 @@ class EncryptService {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function encryptFile($file) {
|
public function encryptFile($file) {
|
||||||
return $this->handleFile($file, 'encrypt');
|
return $this->handleFile($file, EncryptService::OP_ENCRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -357,7 +358,7 @@ class EncryptService {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function decryptFile($file) {
|
public function decryptFile($file) {
|
||||||
return $this->handleFile($file, 'decrypt');
|
return $this->handleFile($file, EncryptService::OP_DECRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -367,12 +368,11 @@ class EncryptService {
|
||||||
* @return File|array
|
* @return File|array
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private function handleFile($file, $op) {
|
private function handleFile($file, $service_function) {
|
||||||
$service_function = ($op === 'encrypt') ? 'encrypt' : 'decrypt';
|
|
||||||
$userKey = '';
|
$userKey = '';
|
||||||
$userSuppliedKey = '';
|
$userSuppliedKey = '';
|
||||||
if ($file instanceof File) {
|
if ($file instanceof File) {
|
||||||
$userSuppliedKey = $file->getSize();
|
$userSuppliedKey = $file->getFilename();
|
||||||
$userKey = md5($file->getMimetype());
|
$userKey = md5($file->getMimetype());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,7 +381,7 @@ class EncryptService {
|
||||||
$userKey = md5($file['mimetype']);
|
$userKey = md5($file['mimetype']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = EncryptService::makeKey($userKey, $this->server_key, $userSuppliedKey);
|
$key = $this->makeKey($userKey, $this->server_key, $userSuppliedKey);
|
||||||
|
|
||||||
|
|
||||||
if ($file instanceof File) {
|
if ($file instanceof File) {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ class SettingsService {
|
||||||
'https_check' => intval($this->config->getAppValue('passman', 'https_check', 1)),
|
'https_check' => intval($this->config->getAppValue('passman', 'https_check', 1)),
|
||||||
'disable_contextmenu' => intval($this->config->getAppValue('passman', 'disable_contextmenu', 1)),
|
'disable_contextmenu' => intval($this->config->getAppValue('passman', 'disable_contextmenu', 1)),
|
||||||
'server_side_encryption' => $this->config->getAppValue('passman', 'server_side_encryption', 'aes-256-cbc'),
|
'server_side_encryption' => $this->config->getAppValue('passman', 'server_side_encryption', 'aes-256-cbc'),
|
||||||
|
'rounds_pbkdf2_stretching' => $this->config->getAppValue('passman', 'rounds_pbkdf2_stretching', 100),
|
||||||
'settings_loaded' => 1
|
'settings_loaded' => 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ class ShareService {
|
||||||
private $revisions;
|
private $revisions;
|
||||||
private $encryptService;
|
private $encryptService;
|
||||||
|
|
||||||
private $server_key;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
SharingACLMapper $sharingACL,
|
SharingACLMapper $sharingACL,
|
||||||
|
|
@ -55,7 +54,6 @@ class ShareService {
|
||||||
$this->credential = $credentials;
|
$this->credential = $credentials;
|
||||||
$this->revisions = $revisions;
|
$this->revisions = $revisions;
|
||||||
$this->encryptService = $encryptService;
|
$this->encryptService = $encryptService;
|
||||||
$this->server_key = \OC::$server->getConfig()->getSystemValue('passwordsalt', '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
59
tests/unit/lib/Service/EncryptServiceTest.php
Normal file
59
tests/unit/lib/Service/EncryptServiceTest.php
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Nextcloud - passman
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2016, Sander Brand (brantje@gmail.com)
|
||||||
|
* @copyright Copyright (c) 2016, Marcos Zuriaga Miguel (wolfi@wolfi.es)
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program 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.
|
||||||
|
*
|
||||||
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Passman\Controller;
|
||||||
|
|
||||||
|
use OCA\Passman\Service\EncryptService;
|
||||||
|
use OCP\AppFramework\Http\JSONResponse;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use PHPUnit_Framework_TestCase;
|
||||||
|
use OCA\Passman\Service\SettingsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SettingsServiceTest
|
||||||
|
*
|
||||||
|
* @package OCA\Passman\Controller
|
||||||
|
* @coversDefaultClass \OCA\Passman\Service\EncryptService
|
||||||
|
*/
|
||||||
|
class EncryptServiceTest extends PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
|
private $service;
|
||||||
|
private $testKey;
|
||||||
|
|
||||||
|
public function setUp() {
|
||||||
|
$config = $this->getMockBuilder('OCP\IConfig')->getMock();
|
||||||
|
$userId = 'admin';
|
||||||
|
$settings_service = new SettingsService($userId, $config, 'passman');
|
||||||
|
$this->service = new EncryptService($settings_service);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::testMakeKey
|
||||||
|
*/
|
||||||
|
public function testMakeKey() {
|
||||||
|
$this->testKey = $this->service->makeKey('userKey', 'serverKey', 'userSuppliedKey');
|
||||||
|
$this->assertTrue($this->testKey === '967efb38599fb81ebc95b280e7c86cda0593e469f6a391caf9e9fee7c3976fd1edcdeefdb6a99e9f0bc47fda4b77fb8309c1955211dccf1dab1aad00c2ad5656');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue