-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add a session wrapper to encrypt the data before storing it on disk #17744
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| <?php | ||
| /** | ||
| * @author Joas Schilling <[email protected]> | ||
| * | ||
| * @copyright Copyright (c) 2015, ownCloud, Inc. | ||
| * @license AGPL-3.0 | ||
| * | ||
| * This code is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License, version 3, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * 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, version 3, | ||
| * along with this program. If not, see <http://www.gnu.org/licenses/> | ||
| * | ||
| */ | ||
|
|
||
| namespace OC\Session; | ||
|
|
||
|
|
||
| use OCP\ISession; | ||
| use OCP\Security\ICrypto; | ||
|
|
||
| class CryptoSessionData implements \ArrayAccess, ISession { | ||
| /** @var ISession */ | ||
| protected $session; | ||
|
|
||
| /** @var \OCP\Security\ICrypto */ | ||
| protected $crypto; | ||
|
|
||
| /** @var string */ | ||
| protected $passphrase; | ||
|
|
||
| /** | ||
| * @param ISession $session | ||
| * @param ICrypto $crypto | ||
| * @param string $passphrase | ||
| */ | ||
| public function __construct(ISession $session, ICrypto $crypto, $passphrase) { | ||
| $this->crypto = $crypto; | ||
| $this->session = $session; | ||
| $this->passphrase = $passphrase; | ||
| } | ||
|
|
||
| /** | ||
| * Set a value in the session | ||
| * | ||
| * @param string $key | ||
| * @param mixed $value | ||
| */ | ||
| public function set($key, $value) { | ||
| $encryptedValue = $this->crypto->encrypt($value, $this->passphrase); | ||
| $this->session->set($key, $encryptedValue); | ||
| } | ||
|
|
||
| /** | ||
| * Get a value from the session | ||
| * | ||
| * @param string $key | ||
| * @return mixed should return null if $key does not exist | ||
| * @throws \Exception when the data could not be decrypted | ||
| */ | ||
| public function get($key) { | ||
| $encryptedValue = $this->session->get($key); | ||
| if ($encryptedValue === null) { | ||
| return null; | ||
| } | ||
|
|
||
| $value = $this->crypto->decrypt($encryptedValue, $this->passphrase); | ||
| return $value; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a named key exists in the session | ||
| * | ||
| * @param string $key | ||
| * @return bool | ||
| */ | ||
| public function exists($key) { | ||
| return $this->session->exists($key); | ||
| } | ||
|
|
||
| /** | ||
| * Remove a $key/$value pair from the session | ||
| * | ||
| * @param string $key | ||
| */ | ||
| public function remove($key) { | ||
| $this->session->remove($key); | ||
| } | ||
|
|
||
| /** | ||
| * Reset and recreate the session | ||
| */ | ||
| public function clear() { | ||
| $this->session->clear(); | ||
| } | ||
|
|
||
| /** | ||
| * Close the session and release the lock | ||
| */ | ||
| public function close() { | ||
| $this->session->close(); | ||
| } | ||
|
|
||
| /** | ||
| * @param mixed $offset | ||
| * @return bool | ||
| */ | ||
| public function offsetExists($offset) { | ||
| return $this->exists($offset); | ||
| } | ||
|
|
||
| /** | ||
| * @param mixed $offset | ||
| * @return mixed | ||
| */ | ||
| public function offsetGet($offset) { | ||
| return $this->get($offset); | ||
| } | ||
|
|
||
| /** | ||
| * @param mixed $offset | ||
| * @param mixed $value | ||
| */ | ||
| public function offsetSet($offset, $value) { | ||
| $this->set($offset, $value); | ||
| } | ||
|
|
||
| /** | ||
| * @param mixed $offset | ||
| */ | ||
| public function offsetUnset($offset) { | ||
| $this->remove($offset); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| <?php | ||
| /** | ||
| * @author Joas Schilling <[email protected]> | ||
| * | ||
| * @copyright Copyright (c) 2015, ownCloud, Inc. | ||
| * @license AGPL-3.0 | ||
| * | ||
| * This code is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License, version 3, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * 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, version 3, | ||
| * along with this program. If not, see <http://www.gnu.org/licenses/> | ||
| * | ||
| */ | ||
|
|
||
| namespace OC\Session; | ||
|
|
||
| use OCP\IConfig; | ||
| use OCP\ISession; | ||
| use OCP\Security\ICrypto; | ||
| use OCP\Security\ISecureRandom; | ||
|
|
||
| class CryptoWrapper { | ||
| const COOKIE_NAME = 'oc_sessionPassphrase'; | ||
|
|
||
| /** @var ISession */ | ||
| protected $session; | ||
|
|
||
| /** @var \OCP\Security\ICrypto */ | ||
| protected $crypto; | ||
|
|
||
| /** @var ISecureRandom */ | ||
| protected $random; | ||
|
|
||
| /** | ||
| * @param IConfig $config | ||
| * @param ICrypto $crypto | ||
| * @param ISecureRandom $random | ||
| */ | ||
| public function __construct(IConfig $config, ICrypto $crypto, ISecureRandom $random) { | ||
| $this->crypto = $crypto; | ||
| $this->config = $config; | ||
| $this->random = $random; | ||
|
|
||
| if (isset($_COOKIE[self::COOKIE_NAME])) { | ||
| // TODO circular dependency | ||
| // $request = \OC::$server->getRequest(); | ||
| // $this->passphrase = $request->getCookie(self::COOKIE_NAME); | ||
| $this->passphrase = $_COOKIE[self::COOKIE_NAME]; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. copied to keep the discussion from @LukasReschke
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Executable samples would be nice, so I can test this. |
||
| } else { | ||
| $this->passphrase = $this->random->getMediumStrengthGenerator()->generate(128); | ||
|
|
||
| // TODO circular dependency | ||
| // $secureCookie = \OC::$server->getRequest()->getServerProtocol() === 'https'; | ||
| $secureCookie = false; | ||
| $expires = time() + $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these occurrences of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or just create a function to set a cookie, which is fed with the seconds until expire, instead of doing it manually all the time.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is a time factory in appframework |
||
|
|
||
| if (!defined('PHPUNIT_RUN')) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Setting a cookie makes the tests fail somehow
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙊 - add a todo?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or fixme
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or try to fix before merge ;) |
||
| setcookie(self::COOKIE_NAME, $this->passphrase, $expires, \OC::$WEBROOT, '', $secureCookie); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param ISession $session | ||
| * @return ISession | ||
| */ | ||
| public function wrapSession(ISession $session) { | ||
| if (!($session instanceof CryptoSessionData) && $this->config->getSystemValue('encrypt.session', false)) { | ||
| return new \OC\Session\CryptoSessionData($session, $this->crypto, $this->passphrase); | ||
| } | ||
|
|
||
| return $session; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| <?php | ||
| /** | ||
| * @author Joas Schilling <[email protected]> | ||
| * | ||
| * @copyright Copyright (c) 2015, ownCloud, Inc. | ||
| * @license AGPL-3.0 | ||
| * | ||
| * This code is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU Affero General Public License, version 3, | ||
| * as published by the Free Software Foundation. | ||
| * | ||
| * 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, version 3, | ||
| * along with this program. If not, see <http://www.gnu.org/licenses/> | ||
| * | ||
| */ | ||
|
|
||
| namespace Test\Session; | ||
|
|
||
| use OC\Session\CryptoSessionData; | ||
|
|
||
| class CryptoSessionDataTest extends Session { | ||
| /** @var \PHPUnit_Framework_MockObject_MockObject|\OCP\Security\ICrypto */ | ||
| protected $crypto; | ||
|
|
||
| /** @var \OCP\ISession */ | ||
| protected $wrappedSession; | ||
|
|
||
| protected function setUp() { | ||
| parent::setUp(); | ||
|
|
||
| $this->wrappedSession = new \OC\Session\Memory($this->getUniqueID()); | ||
| $this->crypto = $this->getMockBuilder('OCP\Security\ICrypto') | ||
| ->disableOriginalConstructor() | ||
| ->getMock(); | ||
| $this->crypto->expects($this->any()) | ||
| ->method('encrypt') | ||
| ->willReturnCallback(function ($input) { | ||
| return '#' . $input . '#'; | ||
| }); | ||
| $this->crypto->expects($this->any()) | ||
| ->method('decrypt') | ||
| ->willReturnCallback(function ($input) { | ||
| return substr($input, 1, -1); | ||
| }); | ||
|
|
||
| $this->instance = new CryptoSessionData($this->wrappedSession, $this->crypto, 'PASS'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the question is shall this be on top of the existing session, or a replacement. When we use the hook, what should happen, if another app already defined a session, or if another app that is loaded later, defines a session (and doesn't wrap it)?
If we scrap the config, doing it after the hook sounds better. The other idea would be to init the session, before the hook, then the wrapper can just wrap it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DeepDiver1975 ^