From 161eaadc898c7f6210c2a10beff774bf569f6b79 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 18 Apr 2016 21:46:07 +0200 Subject: Move OC\IntegrityCheck to PSR-4 --- lib/private/IntegrityCheck/Checker.php | 560 +++++++++++++++++++++ .../Exceptions/InvalidSignatureException.php | 30 ++ lib/private/IntegrityCheck/Helpers/AppLocator.php | 56 +++ .../IntegrityCheck/Helpers/EnvironmentHelper.php | 48 ++ .../IntegrityCheck/Helpers/FileAccessHelper.php | 61 +++ .../Iterator/ExcludeFileByNameFilterIterator.php | 58 +++ .../ExcludeFoldersByPathFilterIterator.php | 61 +++ lib/private/integritycheck/checker.php | 560 --------------------- .../exceptions/invalidsignatureexception.php | 30 -- lib/private/integritycheck/helpers/applocator.php | 56 --- .../integritycheck/helpers/environmenthelper.php | 48 -- .../integritycheck/helpers/fileaccesshelper.php | 61 --- .../iterator/excludefilebynamefilteriterator.php | 58 --- .../excludefoldersbypathfilteriterator.php | 61 --- 14 files changed, 874 insertions(+), 874 deletions(-) create mode 100644 lib/private/IntegrityCheck/Checker.php create mode 100644 lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php create mode 100644 lib/private/IntegrityCheck/Helpers/AppLocator.php create mode 100644 lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php create mode 100644 lib/private/IntegrityCheck/Helpers/FileAccessHelper.php create mode 100644 lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php create mode 100644 lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php delete mode 100644 lib/private/integritycheck/checker.php delete mode 100644 lib/private/integritycheck/exceptions/invalidsignatureexception.php delete mode 100644 lib/private/integritycheck/helpers/applocator.php delete mode 100644 lib/private/integritycheck/helpers/environmenthelper.php delete mode 100644 lib/private/integritycheck/helpers/fileaccesshelper.php delete mode 100644 lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php delete mode 100644 lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php new file mode 100644 index 00000000000..d7867936887 --- /dev/null +++ b/lib/private/IntegrityCheck/Checker.php @@ -0,0 +1,560 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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 OCP\ITempManager; +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; + /** @var ITempManager */ + private $tempManager; + + /** + * @param EnvironmentHelper $environmentHelper + * @param FileAccessHelper $fileAccessHelper + * @param AppLocator $appLocator + * @param IConfig $config + * @param ICacheFactory $cacheFactory + * @param IAppManager $appManager + * @param ITempManager $tempManager + */ + public function __construct(EnvironmentHelper $environmentHelper, + FileAccessHelper $fileAccessHelper, + AppLocator $appLocator, + IConfig $config = null, + ICacheFactory $cacheFactory, + IAppManager $appManager = null, + ITempManager $tempManager) { + $this->environmentHelper = $environmentHelper; + $this->fileAccessHelper = $fileAccessHelper; + $this->appLocator = $appLocator; + $this->config = $config; + $this->cache = $cacheFactory->create(self::CACHE_KEY); + $this->appManager = $appManager; + $this->tempManager = $tempManager; + } + + /** + * Whether code signing is enforced or not. + * + * @return bool + */ + public function isCodeCheckEnforced() { + $signedChannels = [ + 'daily', + 'testing', + 'stable', + ]; + if(!in_array($this->environmentHelper->getChannel(), $signedChannels, true)) { + return false; + } + + /** + * This config option is undocumented and supposed to be so, it's only + * applicable for very specific scenarios and we should not advertise it + * too prominent. So please do not add it to config.sample.php. + */ + $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false); + if($isIntegrityCheckDisabled === true) { + return false; + } + + return true; + } + + /** + * Enumerates all files belonging to the folder. Sensible defaults are excluded. + * + * @param string $folderToIterate + * @param string $root + * @return \RecursiveIteratorIterator + * @throws \Exception + */ + private function getFolderIterator($folderToIterate, $root = '') { + $dirItr = new \RecursiveDirectoryIterator( + $folderToIterate, + \RecursiveDirectoryIterator::SKIP_DOTS + ); + if($root === '') { + $root = \OC::$SERVERROOT; + } + $root = rtrim($root, '/'); + + $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); + $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator, $root); + + 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 = []; + $copiedWebserverSettingFiles = false; + $tmpFolder = ''; + + $baseDirectoryLength = strlen($path); + foreach($iterator as $filename => $data) { + /** @var \DirectoryIterator $data */ + if($data->isDir()) { + continue; + } + + $relativeFileName = substr($filename, $baseDirectoryLength); + $relativeFileName = ltrim($relativeFileName, '/'); + + // 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; + } + + // The .user.ini and the .htaccess file of ownCloud can contain some + // custom modifications such as for example the maximum upload size + // to ensure that this will not lead to false positives this will + // copy the file to a temporary folder and reset it to the default + // values. + if($filename === $this->environmentHelper->getServerRoot() . '/.htaccess' + || $filename === $this->environmentHelper->getServerRoot() . '/.user.ini') { + + if(!$copiedWebserverSettingFiles) { + $tmpFolder = rtrim($this->tempManager->getTemporaryFolder(), '/'); + copy($this->environmentHelper->getServerRoot() . '/.htaccess', $tmpFolder . '/.htaccess'); + copy($this->environmentHelper->getServerRoot() . '/.user.ini', $tmpFolder . '/.user.ini'); + \OC_Files::setUploadLimit( + \OCP\Util::computerFileSize('513MB'), + [ + '.htaccess' => $tmpFolder . '/.htaccess', + '.user.ini' => $tmpFolder . '/.user.ini', + ] + ); + } + } + + // The .user.ini file can contain custom modifications to the file size + // as well. + if($filename === $this->environmentHelper->getServerRoot() . '/.user.ini') { + $fileContent = file_get_contents($tmpFolder . '/.user.ini'); + $hashes[$relativeFileName] = hash('sha512', $fileContent); + continue; + } + + // The .htaccess file in the root folder of ownCloud can contain + // custom content after the installation due to the fact that dynamic + // content is written into it at installation time as well. This + // includes for example the 404 and 403 instructions. + // Thus we ignore everything below the first occurrence of + // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the + // hash generated based on this. + if($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { + $fileContent = file_get_contents($tmpFolder . '/.htaccess'); + $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); + if(count($explodedArray) === 2) { + $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); + 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 app in the specified folder + * + * @param string $path + * @param X509 $certificate + * @param RSA $privateKey + * @throws \Exception + */ + public function writeAppSignature($path, + X509 $certificate, + RSA $privateKey) { + if(!is_dir($path)) { + throw new \Exception('Directory does not exist.'); + } + $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 + * @param string $path + */ + public function writeCoreSignature(X509 $certificate, + RSA $rsa, + $path) { + $iterator = $this->getFolderIterator($path, $path); + $hashes = $this->generateHashes($iterator, $path); + $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); + $this->fileAccessHelper->file_put_contents( + $path . '/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) { + if(!$this->isCodeCheckEnforced()) { + return []; + } + + $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)); + } + + /** + * + * Clean previous results for a proper rescanning. Otherwise + */ + private function cleanResults() { + $this->config->deleteAppValue('core', self::CACHE_KEY); + $this->cache->remove(self::CACHE_KEY); + } + + /** + * 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 + * @param string $path Optional path. If none is given it will be guessed. + * @return array + */ + public function verifyAppSignature($appId, $path = '') { + try { + if($path === '') { + $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->cleanResults(); + $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..521171642b2 --- /dev/null +++ b/lib/private/IntegrityCheck/Exceptions/InvalidSignatureException.php @@ -0,0 +1,30 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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..af22fca1fe4 --- /dev/null +++ b/lib/private/IntegrityCheck/Helpers/AppLocator.php @@ -0,0 +1,56 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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..f56f07486c2 --- /dev/null +++ b/lib/private/IntegrityCheck/Helpers/EnvironmentHelper.php @@ -0,0 +1,48 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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 rtrim(\OC::$SERVERROOT, '/'); + } + + /** + * Provides \OC_Util::getChannel() + * + * @return string + */ + public function getChannel() { + return \OC_Util::getChannel(); + } +} diff --git a/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php b/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php new file mode 100644 index 00000000000..f0bf6576d35 --- /dev/null +++ b/lib/private/IntegrityCheck/Helpers/FileAccessHelper.php @@ -0,0 +1,61 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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..51850852cbd --- /dev/null +++ b/lib/private/IntegrityCheck/Iterator/ExcludeFileByNameFilterIterator.php @@ -0,0 +1,58 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +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..1082e97c296 --- /dev/null +++ b/lib/private/IntegrityCheck/Iterator/ExcludeFoldersByPathFilterIterator.php @@ -0,0 +1,61 @@ + + * + * @copyright Copyright (c) 2016, 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 + * + */ + +namespace OC\IntegrityCheck\Iterator; + +class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator { + private $excludedFolders = []; + + public function __construct(\RecursiveIterator $iterator, $root = '') { + parent::__construct($iterator); + + $appFolders = \OC::$APPSROOTS; + foreach($appFolders as $key => $appFolder) { + $appFolders[$key] = rtrim($appFolder['path'], '/'); + } + + $excludedFolders = [ + rtrim($root . '/data', '/'), + rtrim($root . '/themes', '/'), + rtrim($root . '/config', '/'), + rtrim($root . '/apps', '/'), + rtrim($root . '/assets', '/'), + rtrim($root . '/lost+found', '/'), + ]; + $customDataDir = \OC::$server->getConfig()->getSystemValue('datadirectory', ''); + if($customDataDir !== '') { + $excludedFolders[] = rtrim($customDataDir, '/'); + } + + $this->excludedFolders = array_merge($excludedFolders, $appFolders); + } + + /** + * @return bool + */ + public function accept() { + return !in_array( + $this->current()->getPathName(), + $this->excludedFolders, + true + ); + } +} diff --git a/lib/private/integritycheck/checker.php b/lib/private/integritycheck/checker.php deleted file mode 100644 index d7867936887..00000000000 --- a/lib/private/integritycheck/checker.php +++ /dev/null @@ -1,560 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 OCP\ITempManager; -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; - /** @var ITempManager */ - private $tempManager; - - /** - * @param EnvironmentHelper $environmentHelper - * @param FileAccessHelper $fileAccessHelper - * @param AppLocator $appLocator - * @param IConfig $config - * @param ICacheFactory $cacheFactory - * @param IAppManager $appManager - * @param ITempManager $tempManager - */ - public function __construct(EnvironmentHelper $environmentHelper, - FileAccessHelper $fileAccessHelper, - AppLocator $appLocator, - IConfig $config = null, - ICacheFactory $cacheFactory, - IAppManager $appManager = null, - ITempManager $tempManager) { - $this->environmentHelper = $environmentHelper; - $this->fileAccessHelper = $fileAccessHelper; - $this->appLocator = $appLocator; - $this->config = $config; - $this->cache = $cacheFactory->create(self::CACHE_KEY); - $this->appManager = $appManager; - $this->tempManager = $tempManager; - } - - /** - * Whether code signing is enforced or not. - * - * @return bool - */ - public function isCodeCheckEnforced() { - $signedChannels = [ - 'daily', - 'testing', - 'stable', - ]; - if(!in_array($this->environmentHelper->getChannel(), $signedChannels, true)) { - return false; - } - - /** - * This config option is undocumented and supposed to be so, it's only - * applicable for very specific scenarios and we should not advertise it - * too prominent. So please do not add it to config.sample.php. - */ - $isIntegrityCheckDisabled = $this->config->getSystemValue('integrity.check.disabled', false); - if($isIntegrityCheckDisabled === true) { - return false; - } - - return true; - } - - /** - * Enumerates all files belonging to the folder. Sensible defaults are excluded. - * - * @param string $folderToIterate - * @param string $root - * @return \RecursiveIteratorIterator - * @throws \Exception - */ - private function getFolderIterator($folderToIterate, $root = '') { - $dirItr = new \RecursiveDirectoryIterator( - $folderToIterate, - \RecursiveDirectoryIterator::SKIP_DOTS - ); - if($root === '') { - $root = \OC::$SERVERROOT; - } - $root = rtrim($root, '/'); - - $excludeGenericFilesIterator = new ExcludeFileByNameFilterIterator($dirItr); - $excludeFoldersIterator = new ExcludeFoldersByPathFilterIterator($excludeGenericFilesIterator, $root); - - 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 = []; - $copiedWebserverSettingFiles = false; - $tmpFolder = ''; - - $baseDirectoryLength = strlen($path); - foreach($iterator as $filename => $data) { - /** @var \DirectoryIterator $data */ - if($data->isDir()) { - continue; - } - - $relativeFileName = substr($filename, $baseDirectoryLength); - $relativeFileName = ltrim($relativeFileName, '/'); - - // 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; - } - - // The .user.ini and the .htaccess file of ownCloud can contain some - // custom modifications such as for example the maximum upload size - // to ensure that this will not lead to false positives this will - // copy the file to a temporary folder and reset it to the default - // values. - if($filename === $this->environmentHelper->getServerRoot() . '/.htaccess' - || $filename === $this->environmentHelper->getServerRoot() . '/.user.ini') { - - if(!$copiedWebserverSettingFiles) { - $tmpFolder = rtrim($this->tempManager->getTemporaryFolder(), '/'); - copy($this->environmentHelper->getServerRoot() . '/.htaccess', $tmpFolder . '/.htaccess'); - copy($this->environmentHelper->getServerRoot() . '/.user.ini', $tmpFolder . '/.user.ini'); - \OC_Files::setUploadLimit( - \OCP\Util::computerFileSize('513MB'), - [ - '.htaccess' => $tmpFolder . '/.htaccess', - '.user.ini' => $tmpFolder . '/.user.ini', - ] - ); - } - } - - // The .user.ini file can contain custom modifications to the file size - // as well. - if($filename === $this->environmentHelper->getServerRoot() . '/.user.ini') { - $fileContent = file_get_contents($tmpFolder . '/.user.ini'); - $hashes[$relativeFileName] = hash('sha512', $fileContent); - continue; - } - - // The .htaccess file in the root folder of ownCloud can contain - // custom content after the installation due to the fact that dynamic - // content is written into it at installation time as well. This - // includes for example the 404 and 403 instructions. - // Thus we ignore everything below the first occurrence of - // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the - // hash generated based on this. - if($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { - $fileContent = file_get_contents($tmpFolder . '/.htaccess'); - $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); - if(count($explodedArray) === 2) { - $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); - 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 app in the specified folder - * - * @param string $path - * @param X509 $certificate - * @param RSA $privateKey - * @throws \Exception - */ - public function writeAppSignature($path, - X509 $certificate, - RSA $privateKey) { - if(!is_dir($path)) { - throw new \Exception('Directory does not exist.'); - } - $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 - * @param string $path - */ - public function writeCoreSignature(X509 $certificate, - RSA $rsa, - $path) { - $iterator = $this->getFolderIterator($path, $path); - $hashes = $this->generateHashes($iterator, $path); - $signatureData = $this->createSignatureData($hashes, $certificate, $rsa); - $this->fileAccessHelper->file_put_contents( - $path . '/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) { - if(!$this->isCodeCheckEnforced()) { - return []; - } - - $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)); - } - - /** - * - * Clean previous results for a proper rescanning. Otherwise - */ - private function cleanResults() { - $this->config->deleteAppValue('core', self::CACHE_KEY); - $this->cache->remove(self::CACHE_KEY); - } - - /** - * 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 - * @param string $path Optional path. If none is given it will be guessed. - * @return array - */ - public function verifyAppSignature($appId, $path = '') { - try { - if($path === '') { - $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->cleanResults(); - $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 deleted file mode 100644 index 521171642b2..00000000000 --- a/lib/private/integritycheck/exceptions/invalidsignatureexception.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 deleted file mode 100644 index af22fca1fe4..00000000000 --- a/lib/private/integritycheck/helpers/applocator.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 deleted file mode 100644 index f56f07486c2..00000000000 --- a/lib/private/integritycheck/helpers/environmenthelper.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 rtrim(\OC::$SERVERROOT, '/'); - } - - /** - * Provides \OC_Util::getChannel() - * - * @return string - */ - public function getChannel() { - return \OC_Util::getChannel(); - } -} diff --git a/lib/private/integritycheck/helpers/fileaccesshelper.php b/lib/private/integritycheck/helpers/fileaccesshelper.php deleted file mode 100644 index f0bf6576d35..00000000000 --- a/lib/private/integritycheck/helpers/fileaccesshelper.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 deleted file mode 100644 index 51850852cbd..00000000000 --- a/lib/private/integritycheck/iterator/excludefilebynamefilteriterator.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -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 deleted file mode 100644 index 1082e97c296..00000000000 --- a/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * @copyright Copyright (c) 2016, 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 - * - */ - -namespace OC\IntegrityCheck\Iterator; - -class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator { - private $excludedFolders = []; - - public function __construct(\RecursiveIterator $iterator, $root = '') { - parent::__construct($iterator); - - $appFolders = \OC::$APPSROOTS; - foreach($appFolders as $key => $appFolder) { - $appFolders[$key] = rtrim($appFolder['path'], '/'); - } - - $excludedFolders = [ - rtrim($root . '/data', '/'), - rtrim($root . '/themes', '/'), - rtrim($root . '/config', '/'), - rtrim($root . '/apps', '/'), - rtrim($root . '/assets', '/'), - rtrim($root . '/lost+found', '/'), - ]; - $customDataDir = \OC::$server->getConfig()->getSystemValue('datadirectory', ''); - if($customDataDir !== '') { - $excludedFolders[] = rtrim($customDataDir, '/'); - } - - $this->excludedFolders = array_merge($excludedFolders, $appFolders); - } - - /** - * @return bool - */ - public function accept() { - return !in_array( - $this->current()->getPathName(), - $this->excludedFolders, - true - ); - } -} -- cgit v1.2.3