diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 02d7ef94b7..35f24b188f 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -69,6 +69,9 @@ t('core', '/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in our documentation.', {docLink: data.securityDocs}) ); } + if(data.isUsedTlsLibOutdated) { + messages.push(data.isUsedTlsLibOutdated); + } } else { messages.push(t('core', 'Error occurred while checking server setup')); } diff --git a/settings/application.php b/settings/application.php index a2f25935e1..8da835c18d 100644 --- a/settings/application.php +++ b/settings/application.php @@ -154,7 +154,8 @@ class Application extends App { $c->query('Config'), $c->query('ClientService'), $c->query('URLGenerator'), - $c->query('Util') + $c->query('Util'), + $c->query('L10N') ); }); diff --git a/settings/controller/checksetupcontroller.php b/settings/controller/checksetupcontroller.php index 0b4c72acab..f849e3ed56 100644 --- a/settings/controller/checksetupcontroller.php +++ b/settings/controller/checksetupcontroller.php @@ -23,10 +23,12 @@ namespace OC\Settings\Controller; +use GuzzleHttp\Exception\ClientException; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\Http\Client\IClientService; use OCP\IConfig; +use OCP\IL10N; use OCP\IRequest; use OC_Util; use OCP\IURLGenerator; @@ -43,6 +45,8 @@ class CheckSetupController extends Controller { private $util; /** @var IURLGenerator */ private $urlGenerator; + /** @var IL10N */ + private $l10n; /** * @param string $AppName @@ -51,18 +55,21 @@ class CheckSetupController extends Controller { * @param IClientService $clientService * @param IURLGenerator $urlGenerator * @param \OC_Util $util + * @param IL10N $l10n */ public function __construct($AppName, IRequest $request, IConfig $config, IClientService $clientService, IURLGenerator $urlGenerator, - \OC_Util $util) { + \OC_Util $util, + IL10N $l10n) { parent::__construct($AppName, $request); $this->config = $config; $this->clientService = $clientService; $this->util = $util; $this->urlGenerator = $urlGenerator; + $this->l10n = $l10n; } /** @@ -109,6 +116,66 @@ class CheckSetupController extends Controller { return false; } + /** + * Public for the sake of unit-testing + * + * @return array + */ + public function getCurlVersion() { + return curl_version(); + } + + /** + * Check if the used SSL lib is outdated. Older OpenSSL and NSS versions do + * have multiple bugs which likely lead to problems in combination with + * functionalities required by ownCloud such as SNI. + * + * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546 + * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172 + * @return string + */ + private function isUsedTlsLibOutdated() { + $versionString = $this->getCurlVersion(); + if(isset($versionString['ssl_version'])) { + $versionString = $versionString['ssl_version']; + } else { + return ''; + } + + $features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing'); + if(OC_Util::getEditionString() !== '') { + $features = (string)$this->l10n->t('Federated Cloud Sharing'); + } + + // Check if at least OpenSSL after 1.01d or 1.0.2b + if(strpos($versionString, 'OpenSSL/') === 0) { + $majorVersion = substr($versionString, 8, 5); + $patchRelease = substr($versionString, 13, 6); + + if(($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) || + ($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) { + return (string) $this->l10n->t('cURL is using an outdated %s version (%s). Please update your operating system or features such as %s will not work reliably.', ['OpenSSL', $versionString, $features]); + } + } + + // Check if NSS and perform heuristic check + if(strpos($versionString, 'NSS/') === 0) { + try { + $firstClient = $this->clientService->newClient(); + $firstClient->get('https://www.owncloud.org/'); + + $secondClient = $this->clientService->newClient(); + $secondClient->get('https://owncloud.org/'); + } catch (ClientException $e) { + if($e->getResponse()->getStatusCode() === 400) { + return (string) $this->l10n->t('cURL is using an outdated %s version (%s). Please update your operating system or features such as %s will not work reliably.', ['NSS', $versionString, $features]); + } + } + } + + return ''; + } + /** * @return DataResponse */ @@ -121,6 +188,7 @@ class CheckSetupController extends Controller { 'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'), 'isUrandomAvailable' => $this->isUrandomAvailable(), 'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'), + 'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(), ] ); } diff --git a/tests/settings/controller/CheckSetupControllerTest.php b/tests/settings/controller/CheckSetupControllerTest.php index b39d13ac26..6096aae865 100644 --- a/tests/settings/controller/CheckSetupControllerTest.php +++ b/tests/settings/controller/CheckSetupControllerTest.php @@ -24,6 +24,7 @@ namespace OC\Settings\Controller; use OCP\AppFramework\Http\DataResponse; use OCP\Http\Client\IClientService; use OCP\IConfig; +use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use OC_Util; @@ -47,6 +48,8 @@ class CheckSetupControllerTest extends TestCase { private $urlGenerator; /** @var OC_Util */ private $util; + /** @var IL10N */ + private $l10n; public function setUp() { parent::setUp(); @@ -63,15 +66,24 @@ class CheckSetupControllerTest extends TestCase { ->disableOriginalConstructor()->getMock(); $this->urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') ->disableOriginalConstructor()->getMock(); - - $this->checkSetupController = new CheckSetupController( - 'settings', - $this->request, - $this->config, - $this->clientService, - $this->urlGenerator, - $this->util - ); + $this->l10n = $this->getMockBuilder('\OCP\IL10N') + ->disableOriginalConstructor()->getMock(); + $this->l10n->expects($this->any()) + ->method('t') + ->will($this->returnCallback(function($message, array $replace) { + return vsprintf($message, $replace); + })); + $this->checkSetupController = $this->getMockBuilder('\OC\Settings\Controller\CheckSetupController') + ->setConstructorArgs([ + 'settings', + $this->request, + $this->config, + $this->clientService, + $this->urlGenerator, + $this->util, + $this->l10n, + ]) + ->setMethods(['getCurlVersion'])->getMock(); } public function testIsInternetConnectionWorkingDisabledViaConfig() { @@ -241,8 +253,134 @@ class CheckSetupControllerTest extends TestCase { 'memcacheDocs' => 'http://doc.owncloud.org/server/go.php?to=admin-performance', 'isUrandomAvailable' => self::invokePrivate($this->checkSetupController, 'isUrandomAvailable'), 'securityDocs' => 'https://doc.owncloud.org/server/8.1/admin_manual/configuration_server/hardening.html', + 'isUsedTlsLibOutdated' => '', ] ); $this->assertEquals($expected, $this->checkSetupController->check()); } + + public function testGetCurlVersion() { + $checkSetupController = $this->getMockBuilder('\OC\Settings\Controller\CheckSetupController') + ->setConstructorArgs([ + 'settings', + $this->request, + $this->config, + $this->clientService, + $this->urlGenerator, + $this->util, + $this->l10n, + ]) + ->setMethods(null)->getMock(); + + $this->assertArrayHasKey('ssl_version', $checkSetupController->getCurlVersion()); + } + + public function testIsUsedTlsLibOutdatedWithAnotherLibrary() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'SSLlib'])); + $this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsUsedTlsLibOutdatedWithMisbehavingCurl() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue([])); + $this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsUsedTlsLibOutdatedWithOlderOpenSsl() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'OpenSSL/1.0.1c'])); + $this->assertSame('cURL is using an outdated OpenSSL version (OpenSSL/1.0.1c). Please update your operating system or features such as installing and updating apps via the app store or Federated Cloud Sharing will not work reliably.', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsUsedTlsLibOutdatedWithOlderOpenSsl1() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'OpenSSL/1.0.2a'])); + $this->assertSame('cURL is using an outdated OpenSSL version (OpenSSL/1.0.2a). Please update your operating system or features such as installing and updating apps via the app store or Federated Cloud Sharing will not work reliably.', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsUsedTlsLibOutdatedWithMatchingOpenSslVersion() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'OpenSSL/1.0.1d'])); + $this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsUsedTlsLibOutdatedWithMatchingOpenSslVersion1() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'OpenSSL/1.0.2b'])); + $this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + public function testIsBuggyNss400() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'NSS/1.0.2b'])); + $client = $this->getMockBuilder('\OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $exception = $this->getMockBuilder('\GuzzleHttp\Exception\ClientException') + ->disableOriginalConstructor()->getMock(); + $response = $this->getMockBuilder('\GuzzleHttp\Message\ResponseInterface') + ->disableOriginalConstructor()->getMock(); + $response->expects($this->once()) + ->method('getStatusCode') + ->will($this->returnValue(400)); + $exception->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue($response)); + + $client->expects($this->at(0)) + ->method('get') + ->with('https://www.owncloud.org/', []) + ->will($this->throwException($exception)); + + $this->clientService->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->assertSame('cURL is using an outdated NSS version (NSS/1.0.2b). Please update your operating system or features such as installing and updating apps via the app store or Federated Cloud Sharing will not work reliably.', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } + + + public function testIsBuggyNss200() { + $this->checkSetupController + ->expects($this->once()) + ->method('getCurlVersion') + ->will($this->returnValue(['ssl_version' => 'NSS/1.0.2b'])); + $client = $this->getMockBuilder('\OCP\Http\Client\IClient') + ->disableOriginalConstructor()->getMock(); + $exception = $this->getMockBuilder('\GuzzleHttp\Exception\ClientException') + ->disableOriginalConstructor()->getMock(); + $response = $this->getMockBuilder('\GuzzleHttp\Message\ResponseInterface') + ->disableOriginalConstructor()->getMock(); + $response->expects($this->once()) + ->method('getStatusCode') + ->will($this->returnValue(200)); + $exception->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue($response)); + + $client->expects($this->at(0)) + ->method('get') + ->with('https://www.owncloud.org/', []) + ->will($this->throwException($exception)); + + $this->clientService->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->assertSame('', $this->invokePrivate($this->checkSetupController, 'isUsedTlsLibOutdated')); + } }