diff options
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/AppFramework/DependencyInjection/DIContainer.php | 3 | ||||
-rw-r--r-- | lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php | 29 | ||||
-rw-r--r-- | lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php | 54 | ||||
-rw-r--r-- | lib/private/Security/CSRF/CsrfToken.php | 10 | ||||
-rw-r--r-- | lib/private/Security/CSRF/CsrfTokenManager.php | 13 | ||||
-rw-r--r-- | lib/private/Server.php | 13 |
6 files changed, 116 insertions, 6 deletions
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 21d5eaa9503..97faa0edf49 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -379,7 +379,8 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c['AppName'], $app->isLoggedIn(), $app->isAdminUser(), - $app->getServer()->getContentSecurityPolicyManager() + $app->getServer()->getContentSecurityPolicyManager(), + $app->getServer()->getCsrfTokenManager() ); }); diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 5e253d0954a..6c33c0023ea 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -36,6 +36,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSRF\CsrfTokenManager; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; @@ -77,6 +78,8 @@ class SecurityMiddleware extends Middleware { private $isAdminUser; /** @var ContentSecurityPolicyManager */ private $contentSecurityPolicyManager; + /** @var CsrfTokenManager */ + private $csrfTokenManager; /** * @param IRequest $request @@ -88,6 +91,7 @@ class SecurityMiddleware extends Middleware { * @param bool $isLoggedIn * @param bool $isAdminUser * @param ContentSecurityPolicyManager $contentSecurityPolicyManager + * @param CSRFTokenManager $csrfTokenManager */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -97,7 +101,8 @@ class SecurityMiddleware extends Middleware { $appName, $isLoggedIn, $isAdminUser, - ContentSecurityPolicyManager $contentSecurityPolicyManager) { + ContentSecurityPolicyManager $contentSecurityPolicyManager, + CsrfTokenManager $csrfTokenManager) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -107,6 +112,7 @@ class SecurityMiddleware extends Middleware { $this->isLoggedIn = $isLoggedIn; $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; + $this->csrfTokenManager = $csrfTokenManager; } @@ -171,6 +177,23 @@ class SecurityMiddleware extends Middleware { } + private function browserSupportsCspV3() { + $browserWhitelist = [ + // Chrome 40+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/', + // Firefox 45+ + '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', + // Safari 10+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/', + ]; + + if($this->request->isUserAgent($browserWhitelist)) { + return true; + } + + return false; + } + /** * Performs the default CSP modifications that may be injected by other * applications @@ -190,6 +213,10 @@ class SecurityMiddleware extends Middleware { $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); + if($this->browserSupportsCspV3()) { + $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + $response->setContentSecurityPolicy($defaultPolicy); return $response; diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php new file mode 100644 index 00000000000..0482ea49e5c --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -0,0 +1,54 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @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 OC\Security\CSP; + +use OC\Security\CSRF\CsrfTokenManager; + +/** + * @package OC\Security\CSP + */ +class ContentSecurityPolicyNonceManager { + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var string */ + private $nonce = ''; + + /** + * @param CsrfTokenManager $csrfTokenManager + */ + public function __construct(CsrfTokenManager $csrfTokenManager) { + $this->csrfTokenManager = $csrfTokenManager; + } + + /** + * Returns the current CSP nounce + * + * @return string + */ + public function getNonce() { + if($this->nonce === '') { + $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + + return $this->nonce; + } +} diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php index bf61e339f77..dce9a83b727 100644 --- a/lib/private/Security/CSRF/CsrfToken.php +++ b/lib/private/Security/CSRF/CsrfToken.php @@ -33,6 +33,8 @@ namespace OC\Security\CSRF; class CsrfToken { /** @var string */ private $value; + /** @var string */ + private $encryptedValue = ''; /** * @param string $value Value of the token. Can be encrypted or not encrypted. @@ -48,8 +50,12 @@ class CsrfToken { * @return string */ public function getEncryptedValue() { - $sharedSecret = base64_encode(random_bytes(strlen($this->value))); - return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret; + if($this->encryptedValue === '') { + $sharedSecret = base64_encode(random_bytes(strlen($this->value))); + $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret; + } + + return $this->encryptedValue; } /** diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php index d621cc2c29f..b43ca3d3679 100644 --- a/lib/private/Security/CSRF/CsrfTokenManager.php +++ b/lib/private/Security/CSRF/CsrfTokenManager.php @@ -34,6 +34,8 @@ class CsrfTokenManager { private $tokenGenerator; /** @var SessionStorage */ private $sessionStorage; + /** @var CsrfToken|null */ + private $csrfToken = null; /** * @param CsrfTokenGenerator $tokenGenerator @@ -51,6 +53,10 @@ class CsrfTokenManager { * @return CsrfToken */ public function getToken() { + if(!is_null($this->csrfToken)) { + return $this->csrfToken; + } + if($this->sessionStorage->hasToken()) { $value = $this->sessionStorage->getToken(); } else { @@ -58,7 +64,8 @@ class CsrfTokenManager { $this->sessionStorage->setToken($value); } - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** @@ -69,13 +76,15 @@ class CsrfTokenManager { public function refreshToken() { $value = $this->tokenGenerator->generateToken(); $this->sessionStorage->setToken($value); - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** * Remove the current token from the storage. */ public function removeToken() { + $this->csrfToken = null; $this->sessionStorage->removeToken(); } diff --git a/lib/private/Server.php b/lib/private/Server.php index 11558118d52..1ccc27802d2 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler; use OC\Security\CertificateManager; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\Crypto; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenGenerator; use OC\Security\CSRF\CsrfTokenManager; use OC\Security\CSRF\TokenStorage\SessionStorage; @@ -708,6 +709,11 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('ContentSecurityPolicyManager', function (Server $c) { return new ContentSecurityPolicyManager(); }); + $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) { + return new ContentSecurityPolicyNonceManager( + $c->getCsrfTokenManager() + ); + }); $this->registerService('ShareManager', function(Server $c) { $config = $c->getConfig(); $factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory'); @@ -1406,6 +1412,13 @@ class Server extends ServerContainer implements IServerContainer { } /** + * @return ContentSecurityPolicyNonceManager + */ + public function getContentSecurityPolicyNonceManager() { + return $this->query('ContentSecurityPolicyNonceManager'); + } + + /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\BackendService |