summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/private/integritycheck/checker.php449
-rw-r--r--lib/private/integritycheck/exceptions/invalidsignatureexception.php30
-rw-r--r--lib/private/integritycheck/helpers/applocator.php56
-rw-r--r--lib/private/integritycheck/helpers/environmenthelper.php39
-rw-r--r--lib/private/integritycheck/helpers/fileaccesshelper.php61
-rw-r--r--lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php58
-rw-r--r--lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php51
-rw-r--r--lib/private/server.php31
-rw-r--r--lib/private/templatelayout.php15
-rw-r--r--lib/private/updater.php15
10 files changed, 802 insertions, 3 deletions
diff --git a/lib/private/integritycheck/checker.php b/lib/private/integritycheck/checker.php
new file mode 100644
index 00000000000..edfe6b082e7
--- /dev/null
+++ b/lib/private/integritycheck/checker.php
@@ -0,0 +1,449 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck;
+
+use OC\IntegrityCheck\Exceptions\InvalidSignatureException;
+use OC\IntegrityCheck\Helpers\AppLocator;
+use OC\IntegrityCheck\Helpers\EnvironmentHelper;
+use OC\IntegrityCheck\Helpers\FileAccessHelper;
+use OC\Integritycheck\Iterator\ExcludeFileByNameFilterIterator;
+use OC\IntegrityCheck\Iterator\ExcludeFoldersByPathFilterIterator;
+use OCP\App\IAppManager;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use phpseclib\Crypt\RSA;
+use phpseclib\File\X509;
+
+/**
+ * Class Checker handles the code signing using X.509 and RSA. ownCloud ships with
+ * a public root certificate certificate that allows to issue new certificates that
+ * will be trusted for signing code. The CN will be used to verify that a certificate
+ * given to a third-party developer may not be used for other applications. For
+ * example the author of the application "calendar" would only receive a certificate
+ * only valid for this application.
+ *
+ * @package OC\IntegrityCheck
+ */
+class Checker {
+ const CACHE_KEY = 'oc.integritycheck.checker';
+ /** @var EnvironmentHelper */
+ private $environmentHelper;
+ /** @var AppLocator */
+ private $appLocator;
+ /** @var FileAccessHelper */
+ private $fileAccessHelper;
+ /** @var IConfig */
+ private $config;
+ /** @var ICache */
+ private $cache;
+ /** @var IAppManager */
+ private $appManager;
+
+ /**
+ * @param EnvironmentHelper $environmentHelper
+ * @param FileAccessHelper $fileAccessHelper
+ * @param AppLocator $appLocator
+ * @param IConfig $config
+ * @param ICacheFactory $cacheFactory
+ * @param IAppManager $appManager
+ */
+ public function __construct(EnvironmentHelper $environmentHelper,
+ FileAccessHelper $fileAccessHelper,
+ AppLocator $appLocator,
+ IConfig $config = null,
+ ICacheFactory $cacheFactory,
+ IAppManager $appManager = null) {
+ $this->environmentHelper = $environmentHelper;
+ $this->fileAccessHelper = $fileAccessHelper;
+ $this->appLocator = $appLocator;
+ $this->config = $config;
+ $this->cache = $cacheFactory->create(self::CACHE_KEY);
+ $this->appManager = $appManager;
+ }
+
+ /**
+ * Enumerates all files belonging to the folder. Sensible defaults are excluded.
+ *
+ * @param string $folderToIterate
+ * @return \RecursiveIteratorIterator
+ */
+ private function getFolderIterator($folderToIterate) {
+ $dirItr = new \RecursiveDirectoryIterator(
+ $folderToIterate,
+ \RecursiveDirectoryIterator::SKIP_DOTS
+ );
+ $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr);
+ $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator);
+
+ return new \RecursiveIteratorIterator(
+ $excludeFoldersIterator,
+ \RecursiveIteratorIterator::SELF_FIRST
+ );
+ }
+
+ /**
+ * Returns an array of ['filename' => 'SHA512-hash-of-file'] for all files found
+ * in the iterator.
+ *
+ * @param \RecursiveIteratorIterator $iterator
+ * @param string $path
+ * @return array Array of hashes.
+ */
+ private function generateHashes(\RecursiveIteratorIterator $iterator,
+ $path) {
+ $hashes = [];
+
+ $baseDirectoryLength = strlen($path);
+ foreach($iterator as $filename => $data) {
+ /** @var \DirectoryIterator $data */
+ if($data->isDir()) {
+ continue;
+ }
+
+ $relativeFileName = substr($filename, $baseDirectoryLength);
+
+ // Exclude signature.json files in the appinfo and root folder
+ if($relativeFileName === '/appinfo/signature.json') {
+ continue;
+ }
+ // Exclude signature.json files in the appinfo and core folder
+ if($relativeFileName === '/core/signature.json') {
+ continue;
+ }
+
+ $hashes[$relativeFileName] = hash_file('sha512', $filename);
+ }
+ return $hashes;
+ }
+
+ /**
+ * Creates the signature data
+ *
+ * @param array $hashes
+ * @param X509 $certificate
+ * @param RSA $privateKey
+ * @return string
+ */
+ private function createSignatureData(array $hashes,
+ X509 $certificate,
+ RSA $privateKey) {
+ ksort($hashes);
+
+ $privateKey->setSignatureMode(RSA::SIGNATURE_PSS);
+ $privateKey->setMGFHash('sha512');
+ $signature = $privateKey->sign(json_encode($hashes));
+
+ return [
+ 'hashes' => $hashes,
+ 'signature' => base64_encode($signature),
+ 'certificate' => $certificate->saveX509($certificate->currentCert),
+ ];
+ }
+
+ /**
+ * Write the signature of the specified app
+ *
+ * @param string $appId
+ * @param X509 $certificate
+ * @param RSA $privateKey
+ * @throws \Exception
+ */
+ public function writeAppSignature($appId,
+ X509 $certificate,
+ RSA $privateKey) {
+ $path = $this->appLocator->getAppPath($appId);
+ $iterator = $this->getFolderIterator($path);
+ $hashes = $this->generateHashes($iterator, $path);
+ $signature = $this->createSignatureData($hashes, $certificate, $privateKey);
+ $this->fileAccessHelper->file_put_contents(
+ $path . '/appinfo/signature.json',
+ json_encode($signature, JSON_PRETTY_PRINT)
+ );
+ }
+
+ /**
+ * Write the signature of core
+ *
+ * @param X509 $certificate
+ * @param RSA $rsa
+ */
+ public function writeCoreSignature(X509 $certificate,
+ RSA $rsa) {
+ $iterator = $this->getFolderIterator($this->environmentHelper->getServerRoot());
+ $hashes = $this->generateHashes($iterator, $this->environmentHelper->getServerRoot());
+ $signatureData = $this->createSignatureData($hashes, $certificate, $rsa);
+ $this->fileAccessHelper->file_put_contents(
+ $this->environmentHelper->getServerRoot() . '/core/signature.json',
+ json_encode($signatureData, JSON_PRETTY_PRINT)
+ );
+ }
+
+ /**
+ * Verifies the signature for the specified path.
+ *
+ * @param string $signaturePath
+ * @param string $basePath
+ * @param string $certificateCN
+ * @return array
+ * @throws InvalidSignatureException
+ * @throws \Exception
+ */
+ private function verify($signaturePath, $basePath, $certificateCN) {
+ $signatureData = json_decode($this->fileAccessHelper->file_get_contents($signaturePath), true);
+ if(!is_array($signatureData)) {
+ throw new InvalidSignatureException('Signature data not found.');
+ }
+
+ $expectedHashes = $signatureData['hashes'];
+ ksort($expectedHashes);
+ $signature = base64_decode($signatureData['signature']);
+ $certificate = $signatureData['certificate'];
+
+ // Check if certificate is signed by ownCloud Root Authority
+ $x509 = new \phpseclib\File\X509();
+ $rootCertificatePublicKey = $this->fileAccessHelper->file_get_contents($this->environmentHelper->getServerRoot().'/resources/codesigning/root.crt');
+ $x509->loadCA($rootCertificatePublicKey);
+ $x509->loadX509($certificate);
+ if(!$x509->validateSignature()) {
+ throw new InvalidSignatureException('Certificate is not valid.');
+ }
+ // Verify if certificate has proper CN. "core" CN is always trusted.
+ if($x509->getDN(X509::DN_OPENSSL)['CN'] !== $certificateCN && $x509->getDN(X509::DN_OPENSSL)['CN'] !== 'core') {
+ throw new InvalidSignatureException(
+ sprintf('Certificate is not valid for required scope. (Requested: %s, current: %s)', $certificateCN, $x509->getDN(true))
+ );
+ }
+
+ // Check if the signature of the files is valid
+ $rsa = new \phpseclib\Crypt\RSA();
+ $rsa->loadKey($x509->currentCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']);
+ $rsa->setSignatureMode(RSA::SIGNATURE_PSS);
+ $rsa->setMGFHash('sha512');
+ if(!$rsa->verify(json_encode($expectedHashes), $signature)) {
+ throw new InvalidSignatureException('Signature could not get verified.');
+ }
+
+ // Compare the list of files which are not identical
+ $currentInstanceHashes = $this->generateHashes($this->getFolderIterator($basePath), $basePath);
+ $differencesA = array_diff($expectedHashes, $currentInstanceHashes);
+ $differencesB = array_diff($currentInstanceHashes, $expectedHashes);
+ $differences = array_unique(array_merge($differencesA, $differencesB));
+ $differenceArray = [];
+ foreach($differences as $filename => $hash) {
+ // Check if file should not exist in the new signature table
+ if(!array_key_exists($filename, $expectedHashes)) {
+ $differenceArray['EXTRA_FILE'][$filename]['expected'] = '';
+ $differenceArray['EXTRA_FILE'][$filename]['current'] = $hash;
+ continue;
+ }
+
+ // Check if file is missing
+ if(!array_key_exists($filename, $currentInstanceHashes)) {
+ $differenceArray['FILE_MISSING'][$filename]['expected'] = $expectedHashes[$filename];
+ $differenceArray['FILE_MISSING'][$filename]['current'] = '';
+ continue;
+ }
+
+ // Check if hash does mismatch
+ if($expectedHashes[$filename] !== $currentInstanceHashes[$filename]) {
+ $differenceArray['INVALID_HASH'][$filename]['expected'] = $expectedHashes[$filename];
+ $differenceArray['INVALID_HASH'][$filename]['current'] = $currentInstanceHashes[$filename];
+ continue;
+ }
+
+ // Should never happen.
+ throw new \Exception('Invalid behaviour in file hash comparison experienced. Please report this error to the developers.');
+ }
+
+ return $differenceArray;
+ }
+
+ /**
+ * Whether the code integrity check has passed successful or not
+ *
+ * @return bool
+ */
+ public function hasPassedCheck() {
+ $results = $this->getResults();
+ if(empty($results)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return array
+ */
+ public function getResults() {
+ $cachedResults = $this->cache->get(self::CACHE_KEY);
+ if(!is_null($cachedResults)) {
+ return json_decode($cachedResults, true);
+ }
+
+ return json_decode($this->config->getAppValue('core', self::CACHE_KEY, '{}'), true);
+ }
+
+ /**
+ * Stores the results in the app config as well as cache
+ *
+ * @param string $scope
+ * @param array $result
+ */
+ private function storeResults($scope, array $result) {
+ $resultArray = $this->getResults();
+ unset($resultArray[$scope]);
+ if(!empty($result)) {
+ $resultArray[$scope] = $result;
+ }
+ $this->config->setAppValue('core', self::CACHE_KEY, json_encode($resultArray));
+ $this->cache->set(self::CACHE_KEY, json_encode($resultArray));
+ }
+
+
+ /**
+ * Verify the signature of $appId. Returns an array with the following content:
+ * [
+ * 'FILE_MISSING' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * 'EXTRA_FILE' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * 'INVALID_HASH' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * ]
+ *
+ * Array may be empty in case no problems have been found.
+ *
+ * @param string $appId
+ * @return array
+ */
+ public function verifyAppSignature($appId) {
+ try {
+ $path = $this->appLocator->getAppPath($appId);
+ $result = $this->verify(
+ $path . '/appinfo/signature.json',
+ $path,
+ $appId
+ );
+ } catch (\Exception $e) {
+ $result = [
+ 'EXCEPTION' => [
+ 'class' => get_class($e),
+ 'message' => $e->getMessage(),
+ ],
+ ];
+ }
+ $this->storeResults($appId, $result);
+
+ return $result;
+ }
+
+ /**
+ * Verify the signature of core. Returns an array with the following content:
+ * [
+ * 'FILE_MISSING' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * 'EXTRA_FILE' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * 'INVALID_HASH' =>
+ * [
+ * 'filename' => [
+ * 'expected' => 'expectedSHA512',
+ * 'current' => 'currentSHA512',
+ * ],
+ * ],
+ * ]
+ *
+ * Array may be empty in case no problems have been found.
+ *
+ * @return array
+ */
+ public function verifyCoreSignature() {
+ try {
+ $result = $this->verify(
+ $this->environmentHelper->getServerRoot() . '/core/signature.json',
+ $this->environmentHelper->getServerRoot(),
+ 'core'
+ );
+ } catch (\Exception $e) {
+ $result = [
+ 'EXCEPTION' => [
+ 'class' => get_class($e),
+ 'message' => $e->getMessage(),
+ ],
+ ];
+ }
+ $this->storeResults('core', $result);
+
+ return $result;
+ }
+
+ /**
+ * Verify the core code of the instance as well as all applicable applications
+ * and store the results.
+ */
+ public function runInstanceVerification() {
+ $this->verifyCoreSignature();
+ $appIds = $this->appLocator->getAllApps();
+ foreach($appIds as $appId) {
+ // If an application is shipped a valid signature is required
+ $isShipped = $this->appManager->isShipped($appId);
+ $appNeedsToBeChecked = false;
+ if ($isShipped) {
+ $appNeedsToBeChecked = true;
+ } elseif ($this->fileAccessHelper->file_exists($this->appLocator->getAppPath($appId) . '/appinfo/signature.json')) {
+ // Otherwise only if the application explicitly ships a signature.json file
+ $appNeedsToBeChecked = true;
+ }
+
+ if($appNeedsToBeChecked) {
+ $this->verifyAppSignature($appId);
+ }
+ }
+ }
+}
diff --git a/lib/private/integritycheck/exceptions/invalidsignatureexception.php b/lib/private/integritycheck/exceptions/invalidsignatureexception.php
new file mode 100644
index 00000000000..9e05e5884f5
--- /dev/null
+++ b/lib/private/integritycheck/exceptions/invalidsignatureexception.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Exceptions;
+
+/**
+ * Class InvalidSignatureException is thrown in case the signature of the hashes
+ * cannot be properly validated. This indicates that either files
+ *
+ * @package OC\IntegrityCheck\Exceptions
+ */
+class InvalidSignatureException extends \Exception {}
diff --git a/lib/private/integritycheck/helpers/applocator.php b/lib/private/integritycheck/helpers/applocator.php
new file mode 100644
index 00000000000..b732cb80893
--- /dev/null
+++ b/lib/private/integritycheck/helpers/applocator.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Helpers;
+
+/**
+ * Class AppLocator provides a non-static helper for OC_App::getPath($appId)
+ * it is not possible to use IAppManager at this point as IAppManager has a
+ * dependency on a running ownCloud.
+ *
+ * @package OC\IntegrityCheck\Helpers
+ */
+class AppLocator {
+ /**
+ * Provides \OC_App::getAppPath($appId)
+ *
+ * @param string $appId
+ * @return string
+ * @throws \Exception If the app cannot be found
+ */
+ public function getAppPath($appId) {
+ $path = \OC_App::getAppPath($appId);
+ if($path === false) {
+
+ throw new \Exception('App not found');
+ }
+ return $path;
+ }
+
+ /**
+ * Providers \OC_App::getAllApps()
+ *
+ * @return array
+ */
+ public function getAllApps() {
+ return \OC_App::getAllApps();
+ }
+}
diff --git a/lib/private/integritycheck/helpers/environmenthelper.php b/lib/private/integritycheck/helpers/environmenthelper.php
new file mode 100644
index 00000000000..d7747dbb966
--- /dev/null
+++ b/lib/private/integritycheck/helpers/environmenthelper.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Helpers;
+
+/**
+ * Class EnvironmentHelper provides a non-static helper for access to static
+ * variables such as \OC::$SERVERROOT.
+ *
+ * @package OC\IntegrityCheck\Helpers
+ */
+class EnvironmentHelper {
+ /**
+ * Provides \OC::$SERVERROOT
+ *
+ * @return string
+ */
+ public function getServerRoot() {
+ return \OC::$SERVERROOT;
+ }
+}
diff --git a/lib/private/integritycheck/helpers/fileaccesshelper.php b/lib/private/integritycheck/helpers/fileaccesshelper.php
new file mode 100644
index 00000000000..23f592122dc
--- /dev/null
+++ b/lib/private/integritycheck/helpers/fileaccesshelper.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Helpers;
+
+/**
+ * Class FileAccessHelper provides a helper around file_get_contents and
+ * file_put_contents
+ *
+ * @package OC\IntegrityCheck\Helpers
+ */
+class FileAccessHelper {
+ /**
+ * Wrapper around file_get_contents($filename, $data)
+ *
+ * @param string $filename
+ * @return string|false
+ */
+ public function file_get_contents($filename) {
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Wrapper around file_exists($filename)
+ *
+ * @param string $filename
+ * @return bool
+ */
+ public function file_exists($filename) {
+ return file_exists($filename);
+ }
+
+ /**
+ * Wrapper around file_put_contents($filename, $data)
+ *
+ * @param string $filename
+ * @param $data
+ * @return int|false
+ */
+ public function file_put_contents($filename, $data) {
+ return file_put_contents($filename, $data);
+ }
+}
diff --git a/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php b/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php
new file mode 100644
index 00000000000..c75554a7cc9
--- /dev/null
+++ b/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Iterator;
+
+/**
+ * Class ExcludeFileByNameFilterIterator provides a custom iterator which excludes
+ * entries with the specified file name from the file list.
+ *
+ * @package OC\Integritycheck\Iterator
+ */
+class ExcludeFileByNameFilterIterator extends \RecursiveFilterIterator {
+ /**
+ * Array of excluded file names. Those are not scanned by the integrity checker.
+ * This is used to exclude files which administrators could upload by mistakes
+ * such as .DS_Store files.
+ *
+ * @var array
+ */
+ private $excludedFilenames = [
+ '.DS_Store', // Mac OS X
+ 'Thumbs.db', // Microsoft Windows
+ '.directory', // Dolphin (KDE)
+ ];
+
+ /**
+ * @return bool
+ */
+ public function accept() {
+ if($this->isDir()) {
+ return true;
+ }
+
+ return !in_array(
+ $this->current()->getFilename(),
+ $this->excludedFilenames,
+ true
+ );
+ }
+}
diff --git a/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php
new file mode 100644
index 00000000000..43f19475862
--- /dev/null
+++ b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @author Lukas Reschke <lukas@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\IntegrityCheck\Iterator;
+
+class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator {
+ private $excludedFolders = [];
+
+ public function __construct(\RecursiveIterator $iterator) {
+ parent::__construct($iterator);
+
+ $appFolders = \OC::$APPSROOTS;
+ foreach($appFolders as $key => $appFolder) {
+ $appFolders[$key] = rtrim($appFolder['path'], '/');
+ }
+
+ $this->excludedFolders = array_merge([
+ rtrim(\OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data'), '/'),
+ rtrim(\OC::$SERVERROOT.'/themes', '/'),
+ ], $appFolders);
+ }
+
+ /**
+ * @return bool
+ */
+ public function accept() {
+ return !in_array(
+ $this->current()->getPathName(),
+ $this->excludedFolders,
+ true
+ );
+ }
+}
diff --git a/lib/private/server.php b/lib/private/server.php
index de3324d2cce..fe0d9dcf55d 100644
--- a/lib/private/server.php
+++ b/lib/private/server.php
@@ -51,6 +51,10 @@ use OC\Files\Node\HookConnector;
use OC\Files\Node\Root;
use OC\Files\View;
use OC\Http\Client\ClientService;
+use OC\IntegrityCheck\Checker;
+use OC\IntegrityCheck\Helpers\AppLocator;
+use OC\IntegrityCheck\Helpers\EnvironmentHelper;
+use OC\IntegrityCheck\Helpers\FileAccessHelper;
use OC\Lock\DBLockingProvider;
use OC\Lock\MemcacheLockingProvider;
use OC\Lock\NoopLockingProvider;
@@ -409,6 +413,26 @@ class Server extends SimpleContainer implements IServerContainer {
$this->registerService('TrustedDomainHelper', function ($c) {
return new TrustedDomainHelper($this->getConfig());
});
+ $this->registerService('IntegrityCodeChecker', function (Server $c) {
+ // IConfig and IAppManager requires a working database. This code
+ // might however be called when ownCloud is not yet setup.
+ if(\OC::$server->getSystemConfig()->getValue('installed', false)) {
+ $config = $c->getConfig();
+ $appManager = $c->getAppManager();
+ } else {
+ $config = null;
+ $appManager = null;
+ }
+
+ return new Checker(
+ new EnvironmentHelper(),
+ new FileAccessHelper(),
+ new AppLocator(),
+ $config,
+ $c->getMemCacheFactory(),
+ $appManager
+ );
+ });
$this->registerService('Request', function ($c) {
if (isset($this['urlParams'])) {
$urlParams = $this['urlParams'];
@@ -1094,6 +1118,13 @@ class Server extends SimpleContainer implements IServerContainer {
}
/**
+ * @return \OC\IntegrityCheck\Checker
+ */
+ public function getIntegrityCodeChecker() {
+ return $this->query('IntegrityCodeChecker');
+ }
+
+ /**
* @return \OC\Session\CryptoWrapper
*/
public function getSessionCryptoWrapper() {
diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php
index f5974128b73..1a6a07ddc9f 100644
--- a/lib/private/templatelayout.php
+++ b/lib/private/templatelayout.php
@@ -78,8 +78,12 @@ class OC_TemplateLayout extends OC_Template {
// Update notification
if($this->config->getSystemValue('updatechecker', true) === true &&
OC_User::isAdminUser(OC_User::getUser())) {
- $updater = new \OC\Updater(\OC::$server->getHTTPHelper(),
- \OC::$server->getConfig(), \OC::$server->getLogger());
+ $updater = new \OC\Updater(
+ \OC::$server->getHTTPHelper(),
+ \OC::$server->getConfig(),
+ \OC::$server->getIntegrityCodeChecker(),
+ \OC::$server->getLogger()
+ );
$data = $updater->check();
if(isset($data['version']) && $data['version'] != '' and $data['version'] !== Array()) {
@@ -96,8 +100,13 @@ class OC_TemplateLayout extends OC_Template {
$this->assign('updateAvailable', false); // Update check is disabled
}
- // Add navigation entry
+ // Code integrity notification
+ $integrityChecker = \OC::$server->getIntegrityCodeChecker();
+ if(!$integrityChecker->hasPassedCheck()) {
+ \OCP\Util::addScript('core', 'integritycheck-failed-notification');
+ }
+ // Add navigation entry
$this->assign( 'application', '');
$this->assign( 'appid', $appId );
$navigation = OC_App::getNavigation();
diff --git a/lib/private/updater.php b/lib/private/updater.php
index 1ff80863737..366ad2555a8 100644
--- a/lib/private/updater.php
+++ b/lib/private/updater.php
@@ -34,6 +34,8 @@
namespace OC;
use OC\Hooks\BasicEmitter;
+use OC\IntegrityCheck\Checker;
+use OC\IntegrityCheck\Storage;
use OC_App;
use OC_Installer;
use OC_Util;
@@ -61,6 +63,9 @@ class Updater extends BasicEmitter {
/** @var IConfig */
private $config;
+ /** @var Checker */
+ private $checker;
+
/** @var bool */
private $simulateStepEnabled;
@@ -81,14 +86,17 @@ class Updater extends BasicEmitter {
/**
* @param HTTPHelper $httpHelper
* @param IConfig $config
+ * @param Checker $checker
* @param ILogger $log
*/
public function __construct(HTTPHelper $httpHelper,
IConfig $config,
+ Checker $checker,
ILogger $log = null) {
$this->httpHelper = $httpHelper;
$this->log = $log;
$this->config = $config;
+ $this->checker = $checker;
$this->simulateStepEnabled = true;
$this->updateStepEnabled = true;
}
@@ -335,6 +343,13 @@ class Updater extends BasicEmitter {
//Invalidate update feed
$this->config->setAppValue('core', 'lastupdatedat', 0);
+ // Check for code integrity on the stable channel
+ if(\OC_Util::getChannel() === 'stable') {
+ $this->emit('\OC\Updater', 'startCheckCodeIntegrity');
+ $this->checker->runInstanceVerification();
+ $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity');
+ }
+
// only set the final version if everything went well
$this->config->setSystemValue('version', implode('.', \OC_Util::getVersion()));
}