From 9e6634814ee682be20ac419afeef02a4b53fb47d Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 24 Oct 2016 10:00:00 +0100 Subject: [PATCH 1/3] Add support for CSP nonces CSP nonces are a feature available with CSP v2. Basically instead of saying "JS resources from the same domain are ok to be served" we now say "Ressources from everywhere are allowed as long as they add a `nonce` attribute to the script tag with the right nonce. At the moment the nonce is basically just a ``, we have to decode the requesttoken since `:` is not an allowed value in the nonce. So if somebody does on their own include JS files (instead of using the `addScript` public API, they now must also include that attribute.) IE does currently not implement CSP v2, thus there is a whitelist included that delivers the new CSP v2 policy to newer browsers. Check http://caniuse.com/#feat=contentsecuritypolicy2 for the current browser support list. An alternative approach would be to just add `'unsafe-inline'` as well as `'unsafe-inline'` is ignored by CSPv2 when a nonce is set. But this would make this security feature unusable at all in IE. Not worth it at the moment IMO. Implementing this offers the following advantages: 1. **Security:** As we host resources from the same domain by design we don't have to worry about 'self' anymore being in the whitelist 2. **Performance:** We can move oc.js again to inline JS. This makes the loading way quicker as we don't have to load on every load of a new web page a blocking dynamically non-cached JavaScript file. If you want to toy with CSP see also https://csp-evaluator.withgoogle.com/ Signed-off-by: Lukas Reschke --- apps/theming/appinfo/app.php | 1 + core/templates/layout.base.php | 2 +- core/templates/layout.guest.php | 2 +- core/templates/layout.user.php | 2 +- .../DependencyInjection/DIContainer.php | 3 +- .../Security/SecurityMiddleware.php | 29 +++++++++- lib/private/Security/CSRF/CsrfToken.php | 10 +++- .../Security/CSRF/CsrfTokenManager.php | 13 ++++- .../Http/ContentSecurityPolicy.php | 2 - .../Http/EmptyContentSecurityPolicy.php | 24 +++++++++ .../Http/EmptyContentSecurityPolicyTest.php | 24 +++++++++ .../Security/SecurityMiddlewareTest.php | 53 ++++++++++++++++++- .../Security/CSRF/CsrfTokenManagerTest.php | 16 ++++++ tests/lib/Security/CSRF/CsrfTokenTest.php | 7 +++ 14 files changed, 176 insertions(+), 12 deletions(-) diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php index e67092b642..03fdbc9d00 100644 --- a/apps/theming/appinfo/app.php +++ b/apps/theming/appinfo/app.php @@ -47,6 +47,7 @@ $linkToJs = \OC::$server->getURLGenerator()->linkToRoute( 'script', [ 'src' => $linkToJs, + 'nonce' => base64_encode(\OC::$server->getCsrfTokenManager()->getToken()->getEncryptedValue()) ], '' ); diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php index 7301ae690c..d6fda96dd6 100644 --- a/core/templates/layout.base.php +++ b/core/templates/layout.base.php @@ -19,7 +19,7 @@ - + diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index 5850635315..a93224af5c 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -20,7 +20,7 @@ - + diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 285eb3ab5f..d3dcd979d3 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -27,7 +27,7 @@ - + diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 21d5eaa950..97faa0edf4 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 5e253d0954..6c33c0023e 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/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php index bf61e339f7..dce9a83b72 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 d621cc2c29..b43ca3d367 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/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php index 082aa0206c..17844497f9 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 4fca1588e7..ae4ceef192 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,12 +76,25 @@ 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; return $this; } + /** + * 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 @@ -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); } diff --git a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php index 248c3d808d..33e2315ed8 100644 --- a/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php +++ b/tests/lib/AppFramework/Http/EmptyContentSecurityPolicyTest.php @@ -427,4 +427,28 @@ class EmptyContentSecurityPolicyTest extends \Test\TestCase { $this->contentSecurityPolicy->disallowChildSrcDomain('www.owncloud.org')->disallowChildSrcDomain('www.owncloud.com'); $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); } + + public function testGetPolicyWithJsNonceAndScriptDomains() { + $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl' www.nextcloud.com www.nextcloud.org"; + + $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.com'); + $this->contentSecurityPolicy->useJsNonce('MyJsNonce'); + $this->contentSecurityPolicy->addAllowedScriptDomain('www.nextcloud.org'); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyWithJsNonceAndSelfScriptDomain() { + $expectedPolicy = "default-src 'none';script-src 'nonce-TXlKc05vbmNl'"; + + $this->contentSecurityPolicy->useJsNonce('MyJsNonce'); + $this->contentSecurityPolicy->addAllowedScriptDomain("'self'"); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } + + public function testGetPolicyWithoutJsNonceAndSelfScriptDomain() { + $expectedPolicy = "default-src 'none';script-src 'self'"; + + $this->contentSecurityPolicy->addAllowedScriptDomain("'self'"); + $this->assertSame($expectedPolicy, $this->contentSecurityPolicy->buildPolicy()); + } } diff --git a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php index 55bf3e46e0..b597317fca 100644 --- a/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/SecurityMiddlewareTest.php @@ -36,6 +36,8 @@ use OC\AppFramework\Middleware\Security\SecurityMiddleware; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSP\ContentSecurityPolicy; use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSRF\CsrfToken; +use OC\Security\CSRF\CsrfTokenManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; @@ -72,6 +74,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { private $urlGenerator; /** @var ContentSecurityPolicyManager|\PHPUnit_Framework_MockObject_MockObject */ private $contentSecurityPolicyManager; + /** @var CsrfTokenManager|\PHPUnit_Framework_MockObject_MockObject */ + private $csrfTokenManager; protected function setUp() { parent::setUp(); @@ -83,6 +87,7 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->request = $this->createMock(IRequest::class); $this->contentSecurityPolicyManager = $this->createMock(ContentSecurityPolicyManager::class); + $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); $this->middleware = $this->getMiddleware(true, true); $this->secException = new SecurityException('hey', false); $this->secAjaxException = new SecurityException('hey', true); @@ -103,7 +108,8 @@ class SecurityMiddlewareTest extends \Test\TestCase { 'files', $isLoggedIn, $isAdminUser, - $this->contentSecurityPolicyManager + $this->contentSecurityPolicyManager, + $this->csrfTokenManager ); } @@ -553,6 +559,10 @@ class SecurityMiddlewareTest extends \Test\TestCase { } public function testAfterController() { + $this->request + ->expects($this->once()) + ->method('isUserAgent') + ->willReturn(false); $response = $this->createMock(Response::class); $defaultPolicy = new ContentSecurityPolicy(); $defaultPolicy->addAllowedImageDomain('defaultpolicy'); @@ -591,4 +601,45 @@ class SecurityMiddlewareTest extends \Test\TestCase { $this->middleware->afterController($this->controller, 'test', $response); } + + public function testAfterControllerWithContentSecurityPolicy3Support() { + $this->request + ->expects($this->once()) + ->method('isUserAgent') + ->willReturn(true); + $token = $this->createMock(CsrfToken::class); + $token + ->expects($this->once()) + ->method('getEncryptedValue') + ->willReturn('MyEncryptedToken'); + $this->csrfTokenManager + ->expects($this->once()) + ->method('getToken') + ->willReturn($token); + $response = $this->createMock(Response::class); + $defaultPolicy = new ContentSecurityPolicy(); + $defaultPolicy->addAllowedImageDomain('defaultpolicy'); + $currentPolicy = new ContentSecurityPolicy(); + $currentPolicy->addAllowedConnectDomain('currentPolicy'); + $mergedPolicy = new ContentSecurityPolicy(); + $mergedPolicy->addAllowedMediaDomain('mergedPolicy'); + $response + ->expects($this->exactly(2)) + ->method('getContentSecurityPolicy') + ->willReturn($currentPolicy); + $this->contentSecurityPolicyManager + ->expects($this->once()) + ->method('getDefaultPolicy') + ->willReturn($defaultPolicy); + $this->contentSecurityPolicyManager + ->expects($this->once()) + ->method('mergePolicies') + ->with($defaultPolicy, $currentPolicy) + ->willReturn($mergedPolicy); + $response->expects($this->once()) + ->method('setContentSecurityPolicy') + ->with($mergedPolicy); + + $this->assertEquals($response, $this->middleware->afterController($this->controller, 'test', $response)); + } } diff --git a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php index ab19a43e91..6f7842fdfd 100644 --- a/tests/lib/Security/CSRF/CsrfTokenManagerTest.php +++ b/tests/lib/Security/CSRF/CsrfTokenManagerTest.php @@ -56,6 +56,22 @@ class CsrfTokenManagerTest extends \Test\TestCase { $this->assertEquals($expected, $this->csrfTokenManager->getToken()); } + public function testGetTokenWithExistingTokenKeepsOnSecondRequest() { + $this->storageInterface + ->expects($this->once()) + ->method('hasToken') + ->willReturn(true); + $this->storageInterface + ->expects($this->once()) + ->method('getToken') + ->willReturn('MyExistingToken'); + + $expected = new \OC\Security\CSRF\CsrfToken('MyExistingToken'); + $token = $this->csrfTokenManager->getToken(); + $this->assertSame($token, $this->csrfTokenManager->getToken()); + $this->assertSame($token, $this->csrfTokenManager->getToken()); + } + public function testGetTokenWithoutExistingToken() { $this->storageInterface ->expects($this->once()) diff --git a/tests/lib/Security/CSRF/CsrfTokenTest.php b/tests/lib/Security/CSRF/CsrfTokenTest.php index da640ce505..d19d1de916 100644 --- a/tests/lib/Security/CSRF/CsrfTokenTest.php +++ b/tests/lib/Security/CSRF/CsrfTokenTest.php @@ -28,6 +28,13 @@ class CsrfTokenTest extends \Test\TestCase { $this->assertSame(':', $csrfToken->getEncryptedValue()[16]); } + public function testGetEncryptedValueStaysSameOnSecondRequest() { + $csrfToken = new \OC\Security\CSRF\CsrfToken('MyCsrfToken'); + $tokenValue = $csrfToken->getEncryptedValue(); + $this->assertSame($tokenValue, $csrfToken->getEncryptedValue()); + $this->assertSame($tokenValue, $csrfToken->getEncryptedValue()); + } + public function testGetDecryptedValue() { $csrfToken = new \OC\Security\CSRF\CsrfToken('XlQhHjgWCgBXAEI0Khl+IQEiCXN2LUcDHAQTQAc1HQs=:qgkUlg8l3m8WnkOG4XM9Az33pAt1vSVMx4hcJFsxdqc='); $this->assertSame('/3JKTq2ldmzcDr1f5zDJ7Wt0lEgqqfKF', $csrfToken->getDecryptedValue()); From 38b3ac8213d152a9ea8473cbb558605fb165e91f Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Mon, 24 Oct 2016 16:31:06 +0200 Subject: [PATCH 2/3] Add ContentSecurityPolicyNonceManager Signed-off-by: Lukas Reschke --- apps/theming/appinfo/app.php | 2 +- core/templates/layout.base.php | 2 +- core/templates/layout.guest.php | 2 +- core/templates/layout.user.php | 2 +- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../CSP/ContentSecurityPolicyNonceManager.php | 54 ++++++++++++++++++ lib/private/Server.php | 13 +++++ .../ContentSecurityPolicyNonceManagerTest.php | 57 +++++++++++++++++++ 9 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php create mode 100644 tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php diff --git a/apps/theming/appinfo/app.php b/apps/theming/appinfo/app.php index 03fdbc9d00..152504c417 100644 --- a/apps/theming/appinfo/app.php +++ b/apps/theming/appinfo/app.php @@ -47,7 +47,7 @@ $linkToJs = \OC::$server->getURLGenerator()->linkToRoute( 'script', [ 'src' => $linkToJs, - 'nonce' => base64_encode(\OC::$server->getCsrfTokenManager()->getToken()->getEncryptedValue()) + 'nonce' => \OC::$server->getContentSecurityPolicyNonceManager()->getNonce() ], '' ); diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php index d6fda96dd6..3f13523afc 100644 --- a/core/templates/layout.base.php +++ b/core/templates/layout.base.php @@ -19,7 +19,7 @@ - + diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index a93224af5c..6d46ac6cf2 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -20,7 +20,7 @@ - + diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index d3dcd979d3..d258e3582d 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -27,7 +27,7 @@ - + diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c10f1ca7e5..879630890d 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\\ContentSecurityPolicyNounceManager' => $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 4b7020b59b..281f3d46f0 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\\ContentSecurityPolicyNounceManager' => __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/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php new file mode 100644 index 0000000000..0482ea49e5 --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -0,0 +1,54 @@ + + * + * @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 . + * + */ + +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/Server.php b/lib/private/Server.php index 11558118d5..1ccc27802d 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'); @@ -1405,6 +1411,13 @@ class Server extends ServerContainer implements IServerContainer { return $this->query('ContentSecurityPolicyManager'); } + /** + * @return ContentSecurityPolicyNonceManager + */ + public function getContentSecurityPolicyNonceManager() { + return $this->query('ContentSecurityPolicyNonceManager'); + } + /** * Not a public API as of 8.2, wait for 9.0 * diff --git a/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php new file mode 100644 index 0000000000..39d24807d5 --- /dev/null +++ b/tests/lib/Security/CSP/ContentSecurityPolicyNonceManagerTest.php @@ -0,0 +1,57 @@ + + * + * @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 . + * + */ + +namespace Test\Security\CSP; + +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OC\Security\CSRF\CsrfToken; +use OC\Security\CSRF\CsrfTokenManager; +use Test\TestCase; + +class ContentSecurityPolicyNonceManagerTest extends TestCase { + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var ContentSecurityPolicyNonceManager */ + private $nonceManager; + + public function setUp() { + $this->csrfTokenManager = $this->createMock(CsrfTokenManager::class); + $this->nonceManager = new ContentSecurityPolicyNonceManager( + $this->csrfTokenManager + ); + } + + public function testGetNonce() { + $token = $this->createMock(CsrfToken::class); + $token + ->expects($this->once()) + ->method('getEncryptedValue') + ->willReturn('MyToken'); + + $this->csrfTokenManager + ->expects($this->once()) + ->method('getToken') + ->willReturn($token); + + $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce()); + $this->assertSame('TXlUb2tlbg==', $this->nonceManager->getNonce()); + } +} From ee8b8adf7a62fe4b3823cf803f5f37da3bc6a410 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Mon, 24 Oct 2016 21:50:22 +0200 Subject: [PATCH 3/3] =?UTF-8?q?Update=20autoload=20files=20=F0=9F=99=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Morris Jobke --- lib/composer/composer/autoload_classmap.php | 2 +- lib/composer/composer/autoload_static.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 879630890d..0d2a782a86 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -674,7 +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\\ContentSecurityPolicyNounceManager' => $baseDir . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.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 281f3d46f0..43e3fc3de3 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -704,7 +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\\ContentSecurityPolicyNounceManager' => __DIR__ . '/../../..' . '/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.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',