summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2016-10-25 14:46:00 +0200
committerGitHub <noreply@github.com>2016-10-25 14:46:00 +0200
commit89574367bcc57da5eda6d13ffcfd8a12de68ea26 (patch)
tree8947e9bdc83b55521a4fa52c40e82c6ee7646701 /lib
parent27ba46c40ed1d365965a1cb79ed7d8a38d759d2c (diff)
parentee8b8adf7a62fe4b3823cf803f5f37da3bc6a410 (diff)
downloadnextcloud-server-89574367bcc57da5eda6d13ffcfd8a12de68ea26.tar.gz
nextcloud-server-89574367bcc57da5eda6d13ffcfd8a12de68ea26.zip
Merge pull request #1871 from nextcloud/use-csp-nonces
Use CSP nonces
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php1
-rw-r--r--lib/composer/composer/autoload_static.php1
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php3
-rw-r--r--lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php29
-rw-r--r--lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php54
-rw-r--r--lib/private/Security/CSRF/CsrfToken.php10
-rw-r--r--lib/private/Security/CSRF/CsrfTokenManager.php13
-rw-r--r--lib/private/Server.php13
-rw-r--r--lib/public/AppFramework/Http/ContentSecurityPolicy.php2
-rw-r--r--lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php24
10 files changed, 142 insertions, 8 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index c10f1ca7e5f..0d2a782a867 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -674,6 +674,7 @@ return array(
'OC\\Security\\Bruteforce\\Throttler' => $baseDir . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => $baseDir . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => $baseDir . '/lib/private/Security/CSRF/CsrfTokenManager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 4b7020b59b5..43e3fc3de34 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -704,6 +704,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Security\\Bruteforce\\Throttler' => __DIR__ . '/../../..' . '/lib/private/Security/Bruteforce/Throttler.php',
'OC\\Security\\CSP\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicy.php',
'OC\\Security\\CSP\\ContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyManager.php',
+ 'OC\\Security\\CSP\\ContentSecurityPolicyNonceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php',
'OC\\Security\\CSRF\\CsrfToken' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfToken.php',
'OC\\Security\\CSRF\\CsrfTokenGenerator' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenGenerator.php',
'OC\\Security\\CSRF\\CsrfTokenManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSRF/CsrfTokenManager.php',
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
diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
index 082aa0206c7..17844497f94 100644
--- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php
@@ -24,8 +24,6 @@
namespace OCP\AppFramework\Http;
-use OCP\AppFramework\Http;
-
/**
* Class ContentSecurityPolicy is a simple helper which allows applications to
* modify the Content-Security-Policy sent by ownCloud. Per default only JavaScript,
diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
index 4fca1588e7f..ae4ceef1923 100644
--- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
+++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php
@@ -38,6 +38,8 @@ use OCP\AppFramework\Http;
class EmptyContentSecurityPolicy {
/** @var bool Whether inline JS snippets are allowed */
protected $inlineScriptAllowed = null;
+ /** @var string Whether JS nonces should be used */
+ protected $useJsNonce = null;
/**
* @var bool Whether eval in JS scripts is allowed
* TODO: Disallow per default
@@ -74,6 +76,7 @@ class EmptyContentSecurityPolicy {
* @param bool $state
* @return $this
* @since 8.1.0
+ * @deprecated 10.0 CSP tokens are now used
*/
public function allowInlineScript($state = false) {
$this->inlineScriptAllowed = $state;
@@ -81,6 +84,18 @@ class EmptyContentSecurityPolicy {
}
/**
+ * Use the according JS nonce
+ *
+ * @param string $nonce
+ * @return $this
+ * @since 9.2.0
+ */
+ public function useJsNonce($nonce) {
+ $this->useJsNonce = $nonce;
+ return $this;
+ }
+
+ /**
* Whether eval in JavaScript is allowed or forbidden
* @param bool $state
* @return $this
@@ -323,6 +338,15 @@ class EmptyContentSecurityPolicy {
if(!empty($this->allowedScriptDomains) || $this->inlineScriptAllowed || $this->evalScriptAllowed) {
$policy .= 'script-src ';
+ if(is_string($this->useJsNonce)) {
+ $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\'';
+ $allowedScriptDomains = array_flip($this->allowedScriptDomains);
+ unset($allowedScriptDomains['\'self\'']);
+ $this->allowedScriptDomains = array_flip($allowedScriptDomains);
+ if(count($allowedScriptDomains) !== 0) {
+ $policy .= ' ';
+ }
+ }
if(is_array($this->allowedScriptDomains)) {
$policy .= implode(' ', $this->allowedScriptDomains);
}