Skip to content

Commit

Permalink
Server side checksum verification #26655
Browse files Browse the repository at this point in the history
  • Loading branch information
IljaN authored and Vincent Petry committed Mar 9, 2017
1 parent 1661431 commit 241a172
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 0 deletions.
36 changes: 36 additions & 0 deletions apps/dav/lib/Connector/Sabre/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
namespace OCA\DAV\Connector\Sabre;

use OC\Files\Filesystem;
use OC\Files\Storage\Storage;
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
Expand Down Expand Up @@ -134,6 +135,10 @@ public function put($data) {
list($count, $result) = \OC_Helper::streamCopy($data, $target);
fclose($target);

if (!self::isChecksumValid($partStorage, $internalPartPath)) {
throw new BadRequest('The computed checksum does not match the one received from the client.');
}

if ($result === false) {
$expected = -1;
if (isset($_SERVER['CONTENT_LENGTH'])) {
Expand Down Expand Up @@ -441,6 +446,10 @@ private function createFileChunked($data) {

$chunk_handler->file_assemble($partStorage, $partInternalPath);

if (!self::isChecksumValid($partStorage, $partInternalPath)) {
throw new BadRequest('The computed checksum does not match the one received from the client.');
}

// here is the final atomic rename
$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
$fileExists = $targetStorage->file_exists($targetInternalPath);
Expand Down Expand Up @@ -500,6 +509,33 @@ private function createFileChunked($data) {
return null;
}

/**
* will return true if checksum was not provided in request
*
* @param Storage $storage
* @param $path
* @return bool
*/
private static function isChecksumValid(Storage $storage, $path) {
$meta = $storage->getMetaData($path);
$request = \OC::$server->getRequest();


if (!isset($request->server['HTTP_OC_CHECKSUM']) || !isset($meta['checksum'])) {
// No comparison possible, skip the check
return true;
}

$expectedChecksum = trim($request->server['HTTP_OC_CHECKSUM']);
$computedChecksum = $meta['checksum'];

if ($expectedChecksum !== $computedChecksum) {
return false;
}

return true;
}

/**
* Returns whether a part file is needed for the given storage
* or whether the file can be assembled/uploaded directly on the
Expand Down
4 changes: 4 additions & 0 deletions apps/files/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public function __construct(IConfig $config) {
*/
public function getCapabilities() {
return [
'checksums' => [
'supportedTypes' => ['SHA1'],
'preferredUploadType' => 'SHA1'
],
'files' => [
'bigfilechunking' => true,
'blacklisted_files' => $this->config->getSystemValue('blacklisted_files', ['.htaccess']),
Expand Down
88 changes: 88 additions & 0 deletions lib/private/Files/Storage/Wrapper/Checksum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* @author Ilja Neumann <[email protected]>
*
* @copyright Copyright (c) 2017, ownCloud GmbH.
* @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\Files\Storage\Wrapper;

use OC\Files\Stream\Checksum as ChecksumStream;

/**
* Class Checksum
*
* Computes checksums (default: SHA1) on all files under the /files path.
* The resulting checksum can be retrieved by call getMetadata($path)
*
* @package OC\Files\Storage\Wrapper
*/
class Checksum extends Wrapper {

/**
* @param array $parameters
*/
public function __construct($parameters) {
if (isset($parameters['algo'])) {
ChecksumStream::setAlgo($parameters['algo']);
}
parent::__construct($parameters);
}

/**
* @param string $path
* @param string $mode
* @return false|resource
*/
public function fopen($path, $mode) {
$stream = $this->getWrapperStorage()->fopen($path, $mode);
if (!self::requiresChecksum($path)) {
return $stream;
}

return \OC\Files\Stream\Checksum::wrap($stream, $path);
}


/**
* Checksum is only required for everything under files/
* @param $path
* @return bool
*/
private static function requiresChecksum($path) {
return substr($path, 0, 6) === 'files/';
}

/**
* @param string $path
* @param string $data
* @return bool
*/
public function file_put_contents($path, $data) {
return parent::file_put_contents($path, $data);
}

/**
* @param string $path
* @return array
*/
public function getMetaData($path) {
$parentMetaData = parent::getMetaData($path);
$parentMetaData['checksum'] = ChecksumStream::getChecksum($path);

return $parentMetaData;
}
}
161 changes: 161 additions & 0 deletions lib/private/Files/Stream/Checksum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

/**
* @author Ilja Neumann <[email protected]>
*
* @copyright Copyright (c) 2017, ownCloud GmbH.
* @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\Files\Stream;


use Icewind\Streams\Wrapper;

class Checksum extends Wrapper {

private static $algo = 'sha1';

/** @var resource */
private $hashCtx;

/** @var array Key is path, value is the checksum */
private static $checksums = [];

public function __construct() {
$this->hashCtx = hash_init(self::$algo);
}


/**
* @param $source
* @param $path
* @return resource
*/
public static function wrap($source, $path) {
$context = stream_context_create([
'occhecksum' => [
'source' => $source,
'path' => $path
]
]);

return Wrapper::wrapSource(
$source, $context, 'occhecksum', self::class
);
}


/**
* @param string $path
* @param array $options
* @return bool
*/
public function dir_opendir($path, $options) {
return true;
}

/**
* @param string $path
* @param string $mode
* @param int $options
* @param string $opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path) {
$context = parent::loadContext('occhecksum');
$this->setSourceStream($context['source']);

return true;
}

/**
* @param int $count
* @return string
*/
public function stream_read($count) {
$data = parent::stream_read($count);
hash_update($this->hashCtx, $data);

return $data;
}

/**
* @param string $data
* @return int
*/
public function stream_write($data) {
hash_update($this->hashCtx, $data);

return parent::stream_write($data);
}

/**
* @return bool
*/
public function stream_close() {
self::$checksums[$this->getPathFromContext()] = sprintf(
'%s:%s', strtoupper(self::$algo), hash_final($this->hashCtx)
);

return parent::stream_close();
}

/**
* @return mixed
* @return string
*/
private function getPathFromContext() {
$ctx = stream_context_get_options($this->context);

return $ctx['occhecksum']['path'];
}

/**
* @param $path
* @return string
*/
public static function getChecksum($path) {
if (!isset(self::$checksums[$path])) {
return null;
}

return self::$checksums[$path];
}

/**
* @return array
*/
public static function getChecksums() {
return self::$checksums;
}

/**
* @return string
*/
public static function getAlgo() {
return self::$algo;
}

/**
* @param string $algo
*/
public static function setAlgo($algo) {
self::$algo = $algo;
}


}
6 changes: 6 additions & 0 deletions lib/private/legacy/util.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ public static function setupFS($user = '') {
return $storage;
});

// install storage checksum wrapper
\OC\Files\Filesystem::addStorageWrapper('oc_checksum', function ($mountPoint, $storage) {
return new \OC\Files\Storage\Wrapper\Checksum(['storage' => $storage]);
});


\OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) {
if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]);
Expand Down

0 comments on commit 241a172

Please sign in to comment.