diff options
author | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-14 19:21:18 +0200 |
---|---|---|
committer | Roeland Jago Douma <rullzer@owncloud.com> | 2016-04-14 19:21:18 +0200 |
commit | 9050e76d955e06a5e5ed9b4b1c444bdf03699ba0 (patch) | |
tree | eeded71dd4672fc9dc2f67296b0faff8cd62ab73 /lib/private/Security | |
parent | 5911ce530b003d46348f59e9280b610f684de85a (diff) | |
download | nextcloud-server-9050e76d955e06a5e5ed9b4b1c444bdf03699ba0.tar.gz nextcloud-server-9050e76d955e06a5e5ed9b4b1c444bdf03699ba0.zip |
Move \OC\Security to PSR-4
Diffstat (limited to 'lib/private/Security')
-rw-r--r-- | lib/private/Security/CSP/ContentSecurityPolicy.php | 199 | ||||
-rw-r--r-- | lib/private/Security/CSP/ContentSecurityPolicyManager.php | 73 | ||||
-rw-r--r-- | lib/private/Security/CSRF/CsrfToken.php | 69 | ||||
-rw-r--r-- | lib/private/Security/CSRF/CsrfTokenGenerator.php | 52 | ||||
-rw-r--r-- | lib/private/Security/CSRF/CsrfTokenManager.php | 97 | ||||
-rw-r--r-- | lib/private/Security/CSRF/TokenStorage/SessionStorage.php | 80 | ||||
-rw-r--r-- | lib/private/Security/Certificate.php | 122 | ||||
-rw-r--r-- | lib/private/Security/CertificateManager.php | 250 | ||||
-rw-r--r-- | lib/private/Security/CredentialsManager.php | 125 | ||||
-rw-r--r-- | lib/private/Security/Crypto.php | 134 | ||||
-rw-r--r-- | lib/private/Security/Hasher.php | 160 | ||||
-rw-r--r-- | lib/private/Security/SecureRandom.php | 86 | ||||
-rw-r--r-- | lib/private/Security/TrustedDomainHelper.php | 90 |
13 files changed, 1537 insertions, 0 deletions
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php new file mode 100644 index 00000000000..25eacfab1d6 --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -0,0 +1,199 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSP; + +/** + * Class ContentSecurityPolicy extends the public class and adds getter and setters. + * This is necessary since we don't want to expose the setters and getters to the + * public API. + * + * @package OC\Security\CSP + */ +class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy { + /** + * @return boolean + */ + public function isInlineScriptAllowed() { + return $this->inlineScriptAllowed; + } + + /** + * @param boolean $inlineScriptAllowed + */ + public function setInlineScriptAllowed($inlineScriptAllowed) { + $this->inlineScriptAllowed = $inlineScriptAllowed; + } + + /** + * @return boolean + */ + public function isEvalScriptAllowed() { + return $this->evalScriptAllowed; + } + + /** + * @param boolean $evalScriptAllowed + */ + public function setEvalScriptAllowed($evalScriptAllowed) { + $this->evalScriptAllowed = $evalScriptAllowed; + } + + /** + * @return array + */ + public function getAllowedScriptDomains() { + return $this->allowedScriptDomains; + } + + /** + * @param array $allowedScriptDomains + */ + public function setAllowedScriptDomains($allowedScriptDomains) { + $this->allowedScriptDomains = $allowedScriptDomains; + } + + /** + * @return boolean + */ + public function isInlineStyleAllowed() { + return $this->inlineStyleAllowed; + } + + /** + * @param boolean $inlineStyleAllowed + */ + public function setInlineStyleAllowed($inlineStyleAllowed) { + $this->inlineStyleAllowed = $inlineStyleAllowed; + } + + /** + * @return array + */ + public function getAllowedStyleDomains() { + return $this->allowedStyleDomains; + } + + /** + * @param array $allowedStyleDomains + */ + public function setAllowedStyleDomains($allowedStyleDomains) { + $this->allowedStyleDomains = $allowedStyleDomains; + } + + /** + * @return array + */ + public function getAllowedImageDomains() { + return $this->allowedImageDomains; + } + + /** + * @param array $allowedImageDomains + */ + public function setAllowedImageDomains($allowedImageDomains) { + $this->allowedImageDomains = $allowedImageDomains; + } + + /** + * @return array + */ + public function getAllowedConnectDomains() { + return $this->allowedConnectDomains; + } + + /** + * @param array $allowedConnectDomains + */ + public function setAllowedConnectDomains($allowedConnectDomains) { + $this->allowedConnectDomains = $allowedConnectDomains; + } + + /** + * @return array + */ + public function getAllowedMediaDomains() { + return $this->allowedMediaDomains; + } + + /** + * @param array $allowedMediaDomains + */ + public function setAllowedMediaDomains($allowedMediaDomains) { + $this->allowedMediaDomains = $allowedMediaDomains; + } + + /** + * @return array + */ + public function getAllowedObjectDomains() { + return $this->allowedObjectDomains; + } + + /** + * @param array $allowedObjectDomains + */ + public function setAllowedObjectDomains($allowedObjectDomains) { + $this->allowedObjectDomains = $allowedObjectDomains; + } + + /** + * @return array + */ + public function getAllowedFrameDomains() { + return $this->allowedFrameDomains; + } + + /** + * @param array $allowedFrameDomains + */ + public function setAllowedFrameDomains($allowedFrameDomains) { + $this->allowedFrameDomains = $allowedFrameDomains; + } + + /** + * @return array + */ + public function getAllowedFontDomains() { + return $this->allowedFontDomains; + } + + /** + * @param array $allowedFontDomains + */ + public function setAllowedFontDomains($allowedFontDomains) { + $this->allowedFontDomains = $allowedFontDomains; + } + + /** + * @return array + */ + public function getAllowedChildSrcDomains() { + return $this->allowedChildSrcDomains; + } + + /** + * @param array $allowedChildSrcDomains + */ + public function setAllowedChildSrcDomains($allowedChildSrcDomains) { + $this->allowedChildSrcDomains = $allowedChildSrcDomains; + } + +} diff --git a/lib/private/Security/CSP/ContentSecurityPolicyManager.php b/lib/private/Security/CSP/ContentSecurityPolicyManager.php new file mode 100644 index 00000000000..760cd36e56b --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicyManager.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSP; + +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; +use OCP\Security\IContentSecurityPolicyManager; + +class ContentSecurityPolicyManager implements IContentSecurityPolicyManager { + /** @var ContentSecurityPolicy[] */ + private $policies = []; + + /** {@inheritdoc} */ + public function addDefaultPolicy(EmptyContentSecurityPolicy $policy) { + $this->policies[] = $policy; + } + + /** + * Get the configured default policy. This is not in the public namespace + * as it is only supposed to be used by core itself. + * + * @return ContentSecurityPolicy + */ + public function getDefaultPolicy() { + $defaultPolicy = new \OC\Security\CSP\ContentSecurityPolicy(); + foreach($this->policies as $policy) { + $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy); + } + return $defaultPolicy; + } + + /** + * Merges the first given policy with the second one + * + * @param ContentSecurityPolicy $defaultPolicy + * @param EmptyContentSecurityPolicy $originalPolicy + * @return ContentSecurityPolicy + */ + public function mergePolicies(ContentSecurityPolicy $defaultPolicy, + EmptyContentSecurityPolicy $originalPolicy) { + foreach((object)(array)$originalPolicy as $name => $value) { + $setter = 'set'.ucfirst($name); + if(is_array($value)) { + $getter = 'get'.ucfirst($name); + $currentValues = is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : []; + $defaultPolicy->$setter(array_values(array_unique(array_merge($currentValues, $value)))); + } elseif (is_bool($value)) { + $defaultPolicy->$setter($value); + } + } + + return $defaultPolicy; + } +} diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php new file mode 100644 index 00000000000..4524d0db6e6 --- /dev/null +++ b/lib/private/Security/CSRF/CsrfToken.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSRF; + +/** + * Class CsrfToken represents the stored or provided CSRF token. To mitigate + * BREACH alike vulnerabilities the token is returned in an encrypted value as + * well in an unencrypted value. For display measures to the user always the + * unencrypted one should be chosen. + * + * @package OC\Security\CSRF + */ +class CsrfToken { + /** @var string */ + private $value; + + /** + * @param string $value Value of the token. Can be encrypted or not encrypted. + */ + public function __construct($value) { + $this->value = $value; + } + + /** + * Encrypted value of the token. This is used to mitigate BREACH alike + * vulnerabilities. For display measures do use this functionality. + * + * @return string + */ + public function getEncryptedValue() { + $sharedSecret = base64_encode(random_bytes(strlen($this->value))); + return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret; + } + + /** + * The unencrypted value of the token. Used for decrypting an already + * encrypted token. + * + * @return int + */ + public function getDecryptedValue() { + $token = explode(':', $this->value); + if (count($token) !== 2) { + return ''; + } + $obfuscatedToken = $token[0]; + $secret = $token[1]; + return base64_decode($obfuscatedToken) ^ $secret; + } +} diff --git a/lib/private/Security/CSRF/CsrfTokenGenerator.php b/lib/private/Security/CSRF/CsrfTokenGenerator.php new file mode 100644 index 00000000000..6ea71636d22 --- /dev/null +++ b/lib/private/Security/CSRF/CsrfTokenGenerator.php @@ -0,0 +1,52 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSRF; + +use OCP\Security\ISecureRandom; + +/** + * Class CsrfTokenGenerator is used to generate a cryptographically secure + * pseudo-random number for the token. + * + * @package OC\Security\CSRF + */ +class CsrfTokenGenerator { + /** @var ISecureRandom */ + private $random; + + /** + * @param ISecureRandom $random + */ + public function __construct(ISecureRandom $random) { + $this->random = $random; + } + + /** + * Generate a new CSRF token. + * + * @param int $length Length of the token in characters. + * @return string + */ + public function generateToken($length = 32) { + return $this->random->generate($length); + } +} diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php new file mode 100644 index 00000000000..8d1bf5c0819 --- /dev/null +++ b/lib/private/Security/CSRF/CsrfTokenManager.php @@ -0,0 +1,97 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSRF; + +use OC\Security\CSRF\TokenStorage\SessionStorage; + +/** + * Class CsrfTokenManager is the manager for all CSRF token related activities. + * + * @package OC\Security\CSRF + */ +class CsrfTokenManager { + /** @var CsrfTokenGenerator */ + private $tokenGenerator; + /** @var SessionStorage */ + private $sessionStorage; + + /** + * @param CsrfTokenGenerator $tokenGenerator + * @param SessionStorage $storageInterface + */ + public function __construct(CsrfTokenGenerator $tokenGenerator, + SessionStorage $storageInterface) { + $this->tokenGenerator = $tokenGenerator; + $this->sessionStorage = $storageInterface; + } + + /** + * Returns the current CSRF token, if none set it will create a new one. + * + * @return CsrfToken + */ + public function getToken() { + if($this->sessionStorage->hasToken()) { + $value = $this->sessionStorage->getToken(); + } else { + $value = $this->tokenGenerator->generateToken(); + $this->sessionStorage->setToken($value); + } + + return new CsrfToken($value); + } + + /** + * Invalidates any current token and sets a new one. + * + * @return CsrfToken + */ + public function refreshToken() { + $value = $this->tokenGenerator->generateToken(); + $this->sessionStorage->setToken($value); + return new CsrfToken($value); + } + + /** + * Remove the current token from the storage. + */ + public function removeToken() { + $this->sessionStorage->removeToken(); + } + + /** + * Verifies whether the provided token is valid. + * + * @param CsrfToken $token + * @return bool + */ + public function isTokenValid(CsrfToken $token) { + if(!$this->sessionStorage->hasToken()) { + return false; + } + + return hash_equals( + $this->sessionStorage->getToken(), + $token->getDecryptedValue() + ); + } +} diff --git a/lib/private/Security/CSRF/TokenStorage/SessionStorage.php b/lib/private/Security/CSRF/TokenStorage/SessionStorage.php new file mode 100644 index 00000000000..e1c8c96e920 --- /dev/null +++ b/lib/private/Security/CSRF/TokenStorage/SessionStorage.php @@ -0,0 +1,80 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security\CSRF\TokenStorage; + +use OCP\ISession; + +/** + * Class SessionStorage provides the session storage + * + * @package OC\Security\CSRF\TokenStorage + */ +class SessionStorage { + /** @var ISession */ + private $session; + + /** + * @param ISession $session + */ + public function __construct(ISession $session) { + $this->session = $session; + } + + /** + * Returns the current token or throws an exception if none is found. + * + * @return string + * @throws \Exception + */ + public function getToken() { + $token = $this->session->get('requesttoken'); + if(empty($token)) { + throw new \Exception('Session does not contain a requesttoken'); + } + + return $token; + } + + /** + * Set the valid current token to $value. + * + * @param string $value + */ + public function setToken($value) { + $this->session->set('requesttoken', $value); + } + + /** + * Removes the current token. + */ + public function removeToken() { + $this->session->remove('requesttoken'); + } + /** + * Whether the storage has a storage. + * + * @return bool + */ + public function hasToken() { + return $this->session->exists('requesttoken'); + } +} diff --git a/lib/private/Security/Certificate.php b/lib/private/Security/Certificate.php new file mode 100644 index 00000000000..54486ff51fe --- /dev/null +++ b/lib/private/Security/Certificate.php @@ -0,0 +1,122 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security; + +use OCP\ICertificate; + +class Certificate implements ICertificate { + protected $name; + + protected $commonName; + + protected $organization; + + protected $serial; + + protected $issueDate; + + protected $expireDate; + + protected $issuerName; + + protected $issuerOrganization; + + /** + * @param string $data base64 encoded certificate + * @param string $name + * @throws \Exception If the certificate could not get parsed + */ + public function __construct($data, $name) { + $this->name = $name; + $gmt = new \DateTimeZone('GMT'); + $info = openssl_x509_parse($data); + if(!is_array($info)) { + throw new \Exception('Certificate could not get parsed.'); + } + + $this->commonName = isset($info['subject']['CN']) ? $info['subject']['CN'] : null; + $this->organization = isset($info['subject']['O']) ? $info['subject']['O'] : null; + $this->issueDate = new \DateTime('@' . $info['validFrom_time_t'], $gmt); + $this->expireDate = new \DateTime('@' . $info['validTo_time_t'], $gmt); + $this->issuerName = isset($info['issuer']['CN']) ? $info['issuer']['CN'] : null; + $this->issuerOrganization = isset($info['issuer']['O']) ? $info['issuer']['O'] : null; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @return string|null + */ + public function getCommonName() { + return $this->commonName; + } + + /** + * @return string + */ + public function getOrganization() { + return $this->organization; + } + + /** + * @return \DateTime + */ + public function getIssueDate() { + return $this->issueDate; + } + + /** + * @return \DateTime + */ + public function getExpireDate() { + return $this->expireDate; + } + + /** + * @return bool + */ + public function isExpired() { + $now = new \DateTime(); + return $this->issueDate > $now or $now > $this->expireDate; + } + + /** + * @return string|null + */ + public function getIssuerName() { + return $this->issuerName; + } + + /** + * @return string|null + */ + public function getIssuerOrganization() { + return $this->issuerOrganization; + } +} diff --git a/lib/private/Security/CertificateManager.php b/lib/private/Security/CertificateManager.php new file mode 100644 index 00000000000..f4932ca568e --- /dev/null +++ b/lib/private/Security/CertificateManager.php @@ -0,0 +1,250 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security; + +use OC\Files\Filesystem; +use OCP\ICertificateManager; +use OCP\IConfig; + +/** + * Manage trusted certificates for users + */ +class CertificateManager implements ICertificateManager { + /** + * @var string + */ + protected $uid; + + /** + * @var \OC\Files\View + */ + protected $view; + + /** + * @var IConfig + */ + protected $config; + + /** + * @param string $uid + * @param \OC\Files\View $view relative to data/ + * @param IConfig $config + */ + public function __construct($uid, \OC\Files\View $view, IConfig $config) { + $this->uid = $uid; + $this->view = $view; + $this->config = $config; + } + + /** + * Returns all certificates trusted by the user + * + * @return \OCP\ICertificate[] + */ + public function listCertificates() { + + if (!$this->config->getSystemValue('installed', false)) { + return array(); + } + + $path = $this->getPathToCertificates() . 'uploads/'; + if (!$this->view->is_dir($path)) { + return array(); + } + $result = array(); + $handle = $this->view->opendir($path); + if (!is_resource($handle)) { + return array(); + } + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..') { + try { + $result[] = new Certificate($this->view->file_get_contents($path . $file), $file); + } catch (\Exception $e) { + } + } + } + closedir($handle); + return $result; + } + + /** + * create the certificate bundle of all trusted certificated + */ + public function createCertificateBundle() { + $path = $this->getPathToCertificates(); + $certs = $this->listCertificates(); + + if (!$this->view->file_exists($path)) { + $this->view->mkdir($path); + } + + $fhCerts = $this->view->fopen($path . '/rootcerts.crt', 'w'); + + // Write user certificates + foreach ($certs as $cert) { + $file = $path . '/uploads/' . $cert->getName(); + $data = $this->view->file_get_contents($file); + if (strpos($data, 'BEGIN CERTIFICATE')) { + fwrite($fhCerts, $data); + fwrite($fhCerts, "\r\n"); + } + } + + // Append the default certificates + $defaultCertificates = file_get_contents(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt'); + fwrite($fhCerts, $defaultCertificates); + + // Append the system certificate bundle + $systemBundle = $this->getCertificateBundle(null); + if ($this->view->file_exists($systemBundle)) { + $systemCertificates = $this->view->file_get_contents($systemBundle); + fwrite($fhCerts, $systemCertificates); + } + + fclose($fhCerts); + } + + /** + * Save the certificate and re-generate the certificate bundle + * + * @param string $certificate the certificate data + * @param string $name the filename for the certificate + * @return \OCP\ICertificate + * @throws \Exception If the certificate could not get added + */ + public function addCertificate($certificate, $name) { + if (!Filesystem::isValidPath($name) or Filesystem::isFileBlacklisted($name)) { + throw new \Exception('Filename is not valid'); + } + + $dir = $this->getPathToCertificates() . 'uploads/'; + if (!$this->view->file_exists($dir)) { + $this->view->mkdir($dir); + } + + try { + $file = $dir . $name; + $certificateObject = new Certificate($certificate, $name); + $this->view->file_put_contents($file, $certificate); + $this->createCertificateBundle(); + return $certificateObject; + } catch (\Exception $e) { + throw $e; + } + + } + + /** + * Remove the certificate and re-generate the certificate bundle + * + * @param string $name + * @return bool + */ + public function removeCertificate($name) { + if (!Filesystem::isValidPath($name)) { + return false; + } + $path = $this->getPathToCertificates() . 'uploads/'; + if ($this->view->file_exists($path . $name)) { + $this->view->unlink($path . $name); + $this->createCertificateBundle(); + } + return true; + } + + /** + * Get the path to the certificate bundle for this user + * + * @param string $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle + * @return string + */ + public function getCertificateBundle($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + return $this->getPathToCertificates($uid) . 'rootcerts.crt'; + } + + /** + * Get the full local path to the certificate bundle for this user + * + * @param string $uid (optional) user to get the certificate bundle for, use `null` to get the system bundle + * @return string + */ + public function getAbsoluteBundlePath($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + if ($this->needsRebundling($uid)) { + if (is_null($uid)) { + $manager = new CertificateManager(null, $this->view, $this->config); + $manager->createCertificateBundle(); + } else { + $this->createCertificateBundle(); + } + } + return $this->view->getLocalFile($this->getCertificateBundle($uid)); + } + + /** + * @param string $uid (optional) user to get the certificate path for, use `null` to get the system path + * @return string + */ + private function getPathToCertificates($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + $path = is_null($uid) ? '/files_external/' : '/' . $uid . '/files_external/'; + + return $path; + } + + /** + * Check if we need to re-bundle the certificates because one of the sources has updated + * + * @param string $uid (optional) user to get the certificate path for, use `null` to get the system path + * @return bool + */ + private function needsRebundling($uid = '') { + if ($uid === '') { + $uid = $this->uid; + } + $sourceMTimes = [filemtime(\OC::$SERVERROOT . '/resources/config/ca-bundle.crt')]; + $targetBundle = $this->getCertificateBundle($uid); + if (!$this->view->file_exists($targetBundle)) { + return true; + } + if (!is_null($uid)) { // also depend on the system bundle + $sourceBundles[] = $this->view->filemtime($this->getCertificateBundle(null)); + } + + $sourceMTime = array_reduce($sourceMTimes, function ($max, $mtime) { + return max($max, $mtime); + }, 0); + return $sourceMTime > $this->view->filemtime($targetBundle); + } +} diff --git a/lib/private/Security/CredentialsManager.php b/lib/private/Security/CredentialsManager.php new file mode 100644 index 00000000000..d4104dbe712 --- /dev/null +++ b/lib/private/Security/CredentialsManager.php @@ -0,0 +1,125 @@ +<?php +/** + * @author Robin McCorkell <robin@mccorkell.me.uk> + * + * @copyright Copyright (c) 2016, 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\Security; + +use OCP\Security\ICrypto; +use OCP\IDBConnection; +use OCP\Security\ICredentialsManager; +use OCP\IConfig; + +/** + * Store and retrieve credentials for external services + * + * @package OC\Security + */ +class CredentialsManager implements ICredentialsManager { + + const DB_TABLE = 'credentials'; + + /** @var ICrypto */ + protected $crypto; + + /** @var IDBConnection */ + protected $dbConnection; + + /** + * @param ICrypto $crypto + * @param IDBConnection $dbConnection + */ + public function __construct(ICrypto $crypto, IDBConnection $dbConnection) { + $this->crypto = $crypto; + $this->dbConnection = $dbConnection; + } + + /** + * Store a set of credentials + * + * @param string|null $userId Null for system-wide credentials + * @param string $identifier + * @param mixed $credentials + */ + public function store($userId, $identifier, $credentials) { + $value = $this->crypto->encrypt(json_encode($credentials)); + + $this->dbConnection->setValues(self::DB_TABLE, [ + 'user' => $userId, + 'identifier' => $identifier, + ], [ + 'credentials' => $value, + ]); + } + + /** + * Retrieve a set of credentials + * + * @param string|null $userId Null for system-wide credentials + * @param string $identifier + * @return mixed + */ + public function retrieve($userId, $identifier) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->select('credentials') + ->from(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) + ; + $result = $qb->execute()->fetch(); + + if (!$result) { + return null; + } + $value = $result['credentials']; + + return json_decode($this->crypto->decrypt($value), true); + } + + /** + * Delete a set of credentials + * + * @param string|null $userId Null for system-wide credentials + * @param string $identifier + * @return int rows removed + */ + public function delete($userId, $identifier) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier))) + ; + return $qb->execute(); + } + + /** + * Erase all credentials stored for a user + * + * @param string $userId + * @return int rows removed + */ + public function erase($userId) { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::DB_TABLE) + ->where($qb->expr()->eq('user', $qb->createNamedParameter($userId))) + ; + return $qb->execute(); + } + +} diff --git a/lib/private/Security/Crypto.php b/lib/private/Security/Crypto.php new file mode 100644 index 00000000000..3c3ffb47398 --- /dev/null +++ b/lib/private/Security/Crypto.php @@ -0,0 +1,134 @@ +<?php +/** + * @author Andreas Fischer <bantu@owncloud.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\Security; + +use phpseclib\Crypt\AES; +use phpseclib\Crypt\Hash; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; +use OCP\IConfig; + +/** + * 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 AES $cipher */ + private $cipher; + /** @var int */ + private $ivLength = 16; + /** @var IConfig */ + private $config; + /** @var ISecureRandom */ + private $random; + + /** + * @param IConfig $config + * @param ISecureRandom $random + */ + function __construct(IConfig $config, ISecureRandom $random) { + $this->cipher = new AES(); + $this->config = $config; + $this->random = $random; + } + + /** + * @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 = $this->config->getSystemValue('secret'); + } + + // Append an "a" behind the password and hash it to prevent reusing the same password as for encryption + $password = hash('sha512', $password . 'a'); + + $hash = new 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 = $this->config->getSystemValue('secret'); + } + $this->cipher->setPassword($password); + + $iv = $this->random->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 = $this->config->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(!hash_equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) { + throw new \Exception('HMAC does not match.'); + } + + return $this->cipher->decrypt($ciphertext); + } + +} diff --git a/lib/private/Security/Hasher.php b/lib/private/Security/Hasher.php new file mode 100644 index 00000000000..a8b81aa60eb --- /dev/null +++ b/lib/private/Security/Hasher.php @@ -0,0 +1,160 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, 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\Security; + +use OCP\IConfig; +use OCP\Security\IHasher; + +/** + * Class Hasher provides some basic hashing functions. Furthermore, it supports legacy hashes + * used by previous versions of ownCloud and helps migrating those hashes to newer ones. + * + * The hashes generated by this class are prefixed (version|hash) with a version parameter to allow possible + * updates in the future. + * Possible versions: + * - 1 (Initial version) + * + * Usage: + * // Hashing a message + * $hash = \OC::$server->getHasher()->hash('MessageToHash'); + * // Verifying a message - $newHash will contain the newly calculated hash + * $newHash = null; + * var_dump(\OC::$server->getHasher()->verify('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', $newHash)); + * var_dump($newHash); + * + * @package OC\Security + */ +class Hasher implements IHasher { + /** @var IConfig */ + private $config; + /** @var array Options passed to password_hash and password_needs_rehash */ + private $options = array(); + /** @var string Salt used for legacy passwords */ + private $legacySalt = null; + /** @var int Current version of the generated hash */ + private $currentVersion = 1; + + /** + * @param IConfig $config + */ + function __construct(IConfig $config) { + $this->config = $config; + + $hashingCost = $this->config->getSystemValue('hashingCost', null); + if(!is_null($hashingCost)) { + $this->options['cost'] = $hashingCost; + } + } + + /** + * Hashes a message using PHP's `password_hash` functionality. + * Please note that the size of the returned string is not guaranteed + * and can be up to 255 characters. + * + * @param string $message Message to generate hash from + * @return string Hash of the message with appended version parameter + */ + public function hash($message) { + return $this->currentVersion . '|' . password_hash($message, PASSWORD_DEFAULT, $this->options); + } + + /** + * Get the version and hash from a prefixedHash + * @param string $prefixedHash + * @return null|array Null if the hash is not prefixed, otherwise array('version' => 1, 'hash' => 'foo') + */ + protected function splitHash($prefixedHash) { + $explodedString = explode('|', $prefixedHash, 2); + if(sizeof($explodedString) === 2) { + if((int)$explodedString[0] > 0) { + return array('version' => (int)$explodedString[0], 'hash' => $explodedString[1]); + } + } + + return null; + } + + /** + * Verify legacy hashes + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash + * @return bool Whether $hash is a valid hash of $message + */ + protected function legacyHashVerify($message, $hash, &$newHash = null) { + if(empty($this->legacySalt)) { + $this->legacySalt = $this->config->getSystemValue('passwordsalt', ''); + } + + // Verify whether it matches a legacy PHPass or SHA1 string + $hashLength = strlen($hash); + if($hashLength === 60 && password_verify($message.$this->legacySalt, $hash) || + $hashLength === 40 && hash_equals($hash, sha1($message))) { + $newHash = $this->hash($message); + return true; + } + + return false; + } + + /** + * Verify V1 hashes + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one. + * @return bool Whether $hash is a valid hash of $message + */ + protected function verifyHashV1($message, $hash, &$newHash = null) { + if(password_verify($message, $hash)) { + if(password_needs_rehash($hash, PASSWORD_DEFAULT, $this->options)) { + $newHash = $this->hash($message); + } + return true; + } + + return false; + } + + /** + * @param string $message Message to verify + * @param string $hash Assumed hash of the message + * @param null|string &$newHash Reference will contain the updated hash if necessary. Update the existing hash with this one. + * @return bool Whether $hash is a valid hash of $message + */ + public function verify($message, $hash, &$newHash = null) { + $splittedHash = $this->splitHash($hash); + + if(isset($splittedHash['version'])) { + switch ($splittedHash['version']) { + case 1: + return $this->verifyHashV1($message, $splittedHash['hash'], $newHash); + } + } else { + return $this->legacyHashVerify($message, $hash, $newHash); + } + + + return false; + } + +} diff --git a/lib/private/Security/SecureRandom.php b/lib/private/Security/SecureRandom.php new file mode 100644 index 00000000000..45cb3f17ee4 --- /dev/null +++ b/lib/private/Security/SecureRandom.php @@ -0,0 +1,86 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, 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\Security; + +use RandomLib; +use Sabre\DAV\Exception; +use OCP\Security\ISecureRandom; + +/** + * Class SecureRandom provides a wrapper around the random_int function to generate + * secure random strings. For PHP 7 the native CSPRNG is used, older versions do + * use a fallback. + * + * Usage: + * \OC::$server->getSecureRandom()->generate(10); + * @package OC\Security + */ +class SecureRandom implements 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. + * + * @deprecated 9.0.0 Use \OC\Security\SecureRandom::generate directly or random_bytes() / random_int() + * @return $this + */ + public function 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 + * + * @deprecated 9.0.0 Use \OC\Security\SecureRandom::generate directly or random_bytes() / random_int() + * @return $this + */ + public function getMediumStrengthGenerator() { + return $this; + } + + /** + * Generate a random string of specified length. + * @param int $length The length of the generated string + * @param string $characters An optional list of characters to use if no character list is + * specified all valid base64 characters are used. + * @return string + */ + public function generate($length, + $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') { + $maxCharIndex = strlen($characters) - 1; + $randomString = ''; + + while($length > 0) { + $randomNumber = \random_int(0, $maxCharIndex); + $randomString .= $characters[$randomNumber]; + $length--; + } + return $randomString; + } +} diff --git a/lib/private/Security/TrustedDomainHelper.php b/lib/private/Security/TrustedDomainHelper.php new file mode 100644 index 00000000000..409628677a7 --- /dev/null +++ b/lib/private/Security/TrustedDomainHelper.php @@ -0,0 +1,90 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2016, 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\Security; +use OC\AppFramework\Http\Request; +use OCP\IConfig; + +/** + * Class TrustedDomain + * + * @package OC\Security + */ +class TrustedDomainHelper { + /** @var IConfig */ + private $config; + + /** + * @param IConfig $config + */ + function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * Strips a potential port from a domain (in format domain:port) + * @param string $host + * @return string $host without appended port + */ + private function getDomainWithoutPort($host) { + $pos = strrpos($host, ':'); + if ($pos !== false) { + $port = substr($host, $pos + 1); + if (is_numeric($port)) { + $host = substr($host, 0, $pos); + } + } + return $host; + } + + /** + * Checks whether a domain is considered as trusted from the list + * of trusted domains. If no trusted domains have been configured, returns + * true. + * This is used to prevent Host Header Poisoning. + * @param string $domainWithPort + * @return bool true if the given domain is trusted or if no trusted domains + * have been configured + */ + public function isTrustedDomain($domainWithPort) { + $domain = $this->getDomainWithoutPort($domainWithPort); + + // Read trusted domains from config + $trustedList = $this->config->getSystemValue('trusted_domains', []); + if(!is_array($trustedList)) { + return false; + } + + // Always allow access from localhost + if (preg_match(Request::REGEX_LOCALHOST, $domain) === 1) { + return true; + } + + // Compare with port appended + if(in_array($domainWithPort, $trustedList, true)) { + return true; + } + + return in_array($domain, $trustedList, true); + } + +} |