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 e67092b642f..03fdbc9d002 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 7301ae690cc..d6fda96dd68 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 58506353158..a93224af5cc 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 285eb3ab5f3..d3dcd979d38 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 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/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/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,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 248c3d808d2..33e2315ed89 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 55bf3e46e07..b597317fca4 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 ab19a43e91e..6f7842fdfd9 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 da640ce5052..d19d1de916c 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 03fdbc9d002..152504c4179 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 d6fda96dd68..3f13523afcb 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 a93224af5cc..6d46ac6cf2c 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 d3dcd979d38..d258e3582d0 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 c10f1ca7e5f..879630890d1 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 4b7020b59b5..281f3d46f0d 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 00000000000..0482ea49e5c --- /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 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'); @@ -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 00000000000..39d24807d5b --- /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 879630890d1..0d2a782a867 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 281f3d46f0d..43e3fc3de34 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',