Completed
Push — master ( 6b1ba9...11e998 )
by Björn
17:04
created
lib/private/Files/Stream/Encryption.php 1 patch
Indentation   +483 added lines, -483 removed lines patch added patch discarded remove patch
@@ -33,488 +33,488 @@
 block discarded – undo
33 33
 
34 34
 class Encryption extends Wrapper {
35 35
 
36
-	/** @var \OC\Encryption\Util */
37
-	protected $util;
38
-
39
-	/** @var \OC\Encryption\File */
40
-	protected $file;
41
-
42
-	/** @var \OCP\Encryption\IEncryptionModule */
43
-	protected $encryptionModule;
44
-
45
-	/** @var \OC\Files\Storage\Storage */
46
-	protected $storage;
47
-
48
-	/** @var \OC\Files\Storage\Wrapper\Encryption */
49
-	protected $encryptionStorage;
50
-
51
-	/** @var string */
52
-	protected $internalPath;
53
-
54
-	/** @var string */
55
-	protected $cache;
56
-
57
-	/** @var integer */
58
-	protected $size;
59
-
60
-	/** @var integer */
61
-	protected $position;
62
-
63
-	/** @var integer */
64
-	protected $unencryptedSize;
65
-
66
-	/** @var integer */
67
-	protected $headerSize;
68
-
69
-	/** @var integer */
70
-	protected $unencryptedBlockSize;
71
-
72
-	/** @var array */
73
-	protected $header;
74
-
75
-	/** @var string */
76
-	protected $fullPath;
77
-
78
-	/** @var  bool */
79
-	protected $signed;
80
-
81
-	/**
82
-	 * header data returned by the encryption module, will be written to the file
83
-	 * in case of a write operation
84
-	 *
85
-	 * @var array
86
-	 */
87
-	protected $newHeader;
88
-
89
-	/**
90
-	 * user who perform the read/write operation null for public access
91
-	 *
92
-	 * @var string
93
-	 */
94
-	protected $uid;
95
-
96
-	/** @var bool */
97
-	protected $readOnly;
98
-
99
-	/** @var bool */
100
-	protected $writeFlag;
101
-
102
-	/** @var array */
103
-	protected $expectedContextProperties;
104
-
105
-	/** @var bool */
106
-	protected $fileUpdated;
107
-
108
-	public function __construct() {
109
-		$this->expectedContextProperties = array(
110
-			'source',
111
-			'storage',
112
-			'internalPath',
113
-			'fullPath',
114
-			'encryptionModule',
115
-			'header',
116
-			'uid',
117
-			'file',
118
-			'util',
119
-			'size',
120
-			'unencryptedSize',
121
-			'encryptionStorage',
122
-			'headerSize',
123
-			'signed'
124
-		);
125
-	}
126
-
127
-
128
-	/**
129
-	 * Wraps a stream with the provided callbacks
130
-	 *
131
-	 * @param resource $source
132
-	 * @param string $internalPath relative to mount point
133
-	 * @param string $fullPath relative to data/
134
-	 * @param array $header
135
-	 * @param string $uid
136
-	 * @param \OCP\Encryption\IEncryptionModule $encryptionModule
137
-	 * @param \OC\Files\Storage\Storage $storage
138
-	 * @param \OC\Files\Storage\Wrapper\Encryption $encStorage
139
-	 * @param \OC\Encryption\Util $util
140
-	 * @param \OC\Encryption\File $file
141
-	 * @param string $mode
142
-	 * @param int $size
143
-	 * @param int $unencryptedSize
144
-	 * @param int $headerSize
145
-	 * @param bool $signed
146
-	 * @param string $wrapper stream wrapper class
147
-	 * @return resource
148
-	 *
149
-	 * @throws \BadMethodCallException
150
-	 */
151
-	public static function wrap($source, $internalPath, $fullPath, array $header,
152
-								$uid,
153
-								\OCP\Encryption\IEncryptionModule $encryptionModule,
154
-								\OC\Files\Storage\Storage $storage,
155
-								\OC\Files\Storage\Wrapper\Encryption $encStorage,
156
-								\OC\Encryption\Util $util,
157
-								 \OC\Encryption\File $file,
158
-								$mode,
159
-								$size,
160
-								$unencryptedSize,
161
-								$headerSize,
162
-								$signed,
163
-								$wrapper = Encryption::class) {
164
-
165
-		$context = stream_context_create(array(
166
-			'ocencryption' => array(
167
-				'source' => $source,
168
-				'storage' => $storage,
169
-				'internalPath' => $internalPath,
170
-				'fullPath' => $fullPath,
171
-				'encryptionModule' => $encryptionModule,
172
-				'header' => $header,
173
-				'uid' => $uid,
174
-				'util' => $util,
175
-				'file' => $file,
176
-				'size' => $size,
177
-				'unencryptedSize' => $unencryptedSize,
178
-				'encryptionStorage' => $encStorage,
179
-				'headerSize' => $headerSize,
180
-				'signed' => $signed
181
-			)
182
-		));
183
-
184
-		return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode);
185
-	}
186
-
187
-	/**
188
-	 * add stream wrapper
189
-	 *
190
-	 * @param resource $source
191
-	 * @param string $mode
192
-	 * @param resource $context
193
-	 * @param string $protocol
194
-	 * @param string $class
195
-	 * @return resource
196
-	 * @throws \BadMethodCallException
197
-	 */
198
-	protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') {
199
-		try {
200
-			stream_wrapper_register($protocol, $class);
201
-			if (self::isDirectoryHandle($source)) {
202
-				$wrapped = opendir($protocol . '://', $context);
203
-			} else {
204
-				$wrapped = fopen($protocol . '://', $mode, false, $context);
205
-			}
206
-		} catch (\BadMethodCallException $e) {
207
-			stream_wrapper_unregister($protocol);
208
-			throw $e;
209
-		}
210
-		stream_wrapper_unregister($protocol);
211
-		return $wrapped;
212
-	}
213
-
214
-	/**
215
-	 * Load the source from the stream context and return the context options
216
-	 *
217
-	 * @param string $name
218
-	 * @return array
219
-	 * @throws \BadMethodCallException
220
-	 */
221
-	protected function loadContext($name) {
222
-		$context = parent::loadContext($name);
223
-
224
-		foreach ($this->expectedContextProperties as $property) {
225
-			if (array_key_exists($property, $context)) {
226
-				$this->{$property} = $context[$property];
227
-			} else {
228
-				throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set');
229
-			}
230
-		}
231
-		return $context;
232
-
233
-	}
234
-
235
-	public function stream_open($path, $mode, $options, &$opened_path) {
236
-		$this->loadContext('ocencryption');
237
-
238
-		$this->position = 0;
239
-		$this->cache = '';
240
-		$this->writeFlag = false;
241
-		$this->fileUpdated = false;
242
-		$this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed);
243
-
244
-		if (
245
-			$mode === 'w'
246
-			|| $mode === 'w+'
247
-			|| $mode === 'wb'
248
-			|| $mode === 'wb+'
249
-			|| $mode === 'r+'
250
-			|| $mode === 'rb+'
251
-		) {
252
-			$this->readOnly = false;
253
-		} else {
254
-			$this->readOnly = true;
255
-		}
256
-
257
-		$sharePath = $this->fullPath;
258
-		if (!$this->storage->file_exists($this->internalPath)) {
259
-			$sharePath = dirname($sharePath);
260
-		}
261
-
262
-		$accessList = [];
263
-		if ($this->encryptionModule->needDetailedAccessList()) {
264
-			$accessList = $this->file->getAccessList($sharePath);
265
-		}
266
-		$this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList);
267
-
268
-		if (
269
-			$mode === 'w'
270
-			|| $mode === 'w+'
271
-			|| $mode === 'wb'
272
-			|| $mode === 'wb+'
273
-		) {
274
-			// We're writing a new file so start write counter with 0 bytes
275
-			$this->unencryptedSize = 0;
276
-			$this->writeHeader();
277
-			$this->headerSize = $this->util->getHeaderSize();
278
-			$this->size = $this->headerSize;
279
-		} else {
280
-			$this->skipHeader();
281
-		}
282
-
283
-		return true;
284
-
285
-	}
286
-
287
-	public function stream_eof() {
288
-		return $this->position >= $this->unencryptedSize;
289
-	}
290
-
291
-	public function stream_read($count) {
292
-
293
-		$result = '';
294
-
295
-		$count = min($count, $this->unencryptedSize - $this->position);
296
-		while ($count > 0) {
297
-			$remainingLength = $count;
298
-			// update the cache of the current block
299
-			$this->readCache();
300
-			// determine the relative position in the current block
301
-			$blockPosition = ($this->position % $this->unencryptedBlockSize);
302
-			// if entire read inside current block then only position needs to be updated
303
-			if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
304
-				$result .= substr($this->cache, $blockPosition, $remainingLength);
305
-				$this->position += $remainingLength;
306
-				$count = 0;
307
-				// otherwise remainder of current block is fetched, the block is flushed and the position updated
308
-			} else {
309
-				$result .= substr($this->cache, $blockPosition);
310
-				$this->flush();
311
-				$this->position += ($this->unencryptedBlockSize - $blockPosition);
312
-				$count -= ($this->unencryptedBlockSize - $blockPosition);
313
-			}
314
-		}
315
-		return $result;
316
-
317
-	}
318
-
319
-	public function stream_write($data) {
320
-		$length = 0;
321
-		// loop over $data to fit it in 6126 sized unencrypted blocks
322
-		while (isset($data[0])) {
323
-			$remainingLength = strlen($data);
324
-
325
-			// set the cache to the current 6126 block
326
-			$this->readCache();
327
-
328
-			// for seekable streams the pointer is moved back to the beginning of the encrypted block
329
-			// flush will start writing there when the position moves to another block
330
-			$positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
331
-				$this->util->getBlockSize() + $this->headerSize;
332
-			$resultFseek = $this->parentStreamSeek($positionInFile);
333
-
334
-			// only allow writes on seekable streams, or at the end of the encrypted stream
335
-			if (!$this->readOnly && ($resultFseek || $positionInFile === $this->size)) {
336
-
337
-				// switch the writeFlag so flush() will write the block
338
-				$this->writeFlag = true;
339
-				$this->fileUpdated = true;
340
-
341
-				// determine the relative position in the current block
342
-				$blockPosition = ($this->position % $this->unencryptedBlockSize);
343
-				// check if $data fits in current block
344
-				// if so, overwrite existing data (if any)
345
-				// update position and liberate $data
346
-				if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
347
-					$this->cache = substr($this->cache, 0, $blockPosition)
348
-						. $data . substr($this->cache, $blockPosition + $remainingLength);
349
-					$this->position += $remainingLength;
350
-					$length += $remainingLength;
351
-					$data = '';
352
-					// if $data doesn't fit the current block, the fill the current block and reiterate
353
-					// after the block is filled, it is flushed and $data is updatedxxx
354
-				} else {
355
-					$this->cache = substr($this->cache, 0, $blockPosition) .
356
-						substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
357
-					$this->flush();
358
-					$this->position += ($this->unencryptedBlockSize - $blockPosition);
359
-					$length += ($this->unencryptedBlockSize - $blockPosition);
360
-					$data = substr($data, $this->unencryptedBlockSize - $blockPosition);
361
-				}
362
-			} else {
363
-				$data = '';
364
-			}
365
-			$this->unencryptedSize = max($this->unencryptedSize, $this->position);
366
-		}
367
-		return $length;
368
-	}
369
-
370
-	public function stream_tell() {
371
-		return $this->position;
372
-	}
373
-
374
-	public function stream_seek($offset, $whence = SEEK_SET) {
375
-
376
-		$return = false;
377
-
378
-		switch ($whence) {
379
-			case SEEK_SET:
380
-				$newPosition = $offset;
381
-				break;
382
-			case SEEK_CUR:
383
-				$newPosition = $this->position + $offset;
384
-				break;
385
-			case SEEK_END:
386
-				$newPosition = $this->unencryptedSize + $offset;
387
-				break;
388
-			default:
389
-				return $return;
390
-		}
391
-
392
-		if ($newPosition > $this->unencryptedSize || $newPosition < 0) {
393
-			return $return;
394
-		}
395
-
396
-		$newFilePosition = floor($newPosition / $this->unencryptedBlockSize)
397
-			* $this->util->getBlockSize() + $this->headerSize;
398
-
399
-		$oldFilePosition = parent::stream_tell();
400
-		if ($this->parentStreamSeek($newFilePosition)) {
401
-			$this->parentStreamSeek($oldFilePosition);
402
-			$this->flush();
403
-			$this->parentStreamSeek($newFilePosition);
404
-			$this->position = $newPosition;
405
-			$return = true;
406
-		}
407
-		return $return;
408
-
409
-	}
410
-
411
-	public function stream_close() {
412
-		$this->flush('end');
413
-		$position = (int)floor($this->position/$this->unencryptedBlockSize);
414
-		$remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
415
-		if ($this->readOnly === false) {
416
-			if(!empty($remainingData)) {
417
-				parent::stream_write($remainingData);
418
-			}
419
-			$this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize);
420
-		}
421
-		$result = parent::stream_close();
422
-
423
-		if ($this->fileUpdated) {
424
-			$cache = $this->storage->getCache();
425
-			$cacheEntry = $cache->get($this->internalPath);
426
-			if ($cacheEntry) {
427
-				$version = $cacheEntry['encryptedVersion'] + 1;
428
-				$cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
429
-			}
430
-		}
431
-
432
-		return $result;
433
-	}
434
-
435
-	/**
436
-	 * write block to file
437
-	 * @param string $positionPrefix
438
-	 */
439
-	protected function flush($positionPrefix = '') {
440
-		// write to disk only when writeFlag was set to 1
441
-		if ($this->writeFlag) {
442
-			// Disable the file proxies so that encryption is not
443
-			// automatically attempted when the file is written to disk -
444
-			// we are handling that separately here and we don't want to
445
-			// get into an infinite loop
446
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
447
-			$encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
448
-			$bytesWritten = parent::stream_write($encrypted);
449
-			$this->writeFlag = false;
450
-			// Check whether the write concerns the last block
451
-			// If so then update the encrypted filesize
452
-			// Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called
453
-			// We recalculate the encrypted filesize as we do not know the context of calling flush()
454
-			$completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize);
455
-			if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) {
456
-				$this->size = $this->util->getBlockSize() * $completeBlocksInFile;
457
-				$this->size += $bytesWritten;
458
-				$this->size += $this->headerSize;
459
-			}
460
-		}
461
-		// always empty the cache (otherwise readCache() will not fill it with the new block)
462
-		$this->cache = '';
463
-	}
464
-
465
-	/**
466
-	 * read block to file
467
-	 */
468
-	protected function readCache() {
469
-		// cache should always be empty string when this function is called
470
-		// don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block
471
-		if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
472
-			// Get the data from the file handle
473
-			$data = parent::stream_read($this->util->getBlockSize());
474
-			$position = (int)floor($this->position/$this->unencryptedBlockSize);
475
-			$numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
476
-			if($numberOfChunks === $position) {
477
-				$position .= 'end';
478
-			}
479
-			$this->cache = $this->encryptionModule->decrypt($data, $position);
480
-		}
481
-	}
482
-
483
-	/**
484
-	 * write header at beginning of encrypted file
485
-	 *
486
-	 * @return integer
487
-	 * @throws EncryptionHeaderKeyExistsException if header key is already in use
488
-	 */
489
-	protected function writeHeader() {
490
-		$header = $this->util->createHeader($this->newHeader, $this->encryptionModule);
491
-		return parent::stream_write($header);
492
-	}
493
-
494
-	/**
495
-	 * read first block to skip the header
496
-	 */
497
-	protected function skipHeader() {
498
-		parent::stream_read($this->headerSize);
499
-	}
500
-
501
-	/**
502
-	 * call stream_seek() from parent class
503
-	 *
504
-	 * @param integer $position
505
-	 * @return bool
506
-	 */
507
-	protected function parentStreamSeek($position) {
508
-		return parent::stream_seek($position);
509
-	}
510
-
511
-	/**
512
-	 * @param string $path
513
-	 * @param array $options
514
-	 * @return bool
515
-	 */
516
-	public function dir_opendir($path, $options) {
517
-		return false;
518
-	}
36
+    /** @var \OC\Encryption\Util */
37
+    protected $util;
38
+
39
+    /** @var \OC\Encryption\File */
40
+    protected $file;
41
+
42
+    /** @var \OCP\Encryption\IEncryptionModule */
43
+    protected $encryptionModule;
44
+
45
+    /** @var \OC\Files\Storage\Storage */
46
+    protected $storage;
47
+
48
+    /** @var \OC\Files\Storage\Wrapper\Encryption */
49
+    protected $encryptionStorage;
50
+
51
+    /** @var string */
52
+    protected $internalPath;
53
+
54
+    /** @var string */
55
+    protected $cache;
56
+
57
+    /** @var integer */
58
+    protected $size;
59
+
60
+    /** @var integer */
61
+    protected $position;
62
+
63
+    /** @var integer */
64
+    protected $unencryptedSize;
65
+
66
+    /** @var integer */
67
+    protected $headerSize;
68
+
69
+    /** @var integer */
70
+    protected $unencryptedBlockSize;
71
+
72
+    /** @var array */
73
+    protected $header;
74
+
75
+    /** @var string */
76
+    protected $fullPath;
77
+
78
+    /** @var  bool */
79
+    protected $signed;
80
+
81
+    /**
82
+     * header data returned by the encryption module, will be written to the file
83
+     * in case of a write operation
84
+     *
85
+     * @var array
86
+     */
87
+    protected $newHeader;
88
+
89
+    /**
90
+     * user who perform the read/write operation null for public access
91
+     *
92
+     * @var string
93
+     */
94
+    protected $uid;
95
+
96
+    /** @var bool */
97
+    protected $readOnly;
98
+
99
+    /** @var bool */
100
+    protected $writeFlag;
101
+
102
+    /** @var array */
103
+    protected $expectedContextProperties;
104
+
105
+    /** @var bool */
106
+    protected $fileUpdated;
107
+
108
+    public function __construct() {
109
+        $this->expectedContextProperties = array(
110
+            'source',
111
+            'storage',
112
+            'internalPath',
113
+            'fullPath',
114
+            'encryptionModule',
115
+            'header',
116
+            'uid',
117
+            'file',
118
+            'util',
119
+            'size',
120
+            'unencryptedSize',
121
+            'encryptionStorage',
122
+            'headerSize',
123
+            'signed'
124
+        );
125
+    }
126
+
127
+
128
+    /**
129
+     * Wraps a stream with the provided callbacks
130
+     *
131
+     * @param resource $source
132
+     * @param string $internalPath relative to mount point
133
+     * @param string $fullPath relative to data/
134
+     * @param array $header
135
+     * @param string $uid
136
+     * @param \OCP\Encryption\IEncryptionModule $encryptionModule
137
+     * @param \OC\Files\Storage\Storage $storage
138
+     * @param \OC\Files\Storage\Wrapper\Encryption $encStorage
139
+     * @param \OC\Encryption\Util $util
140
+     * @param \OC\Encryption\File $file
141
+     * @param string $mode
142
+     * @param int $size
143
+     * @param int $unencryptedSize
144
+     * @param int $headerSize
145
+     * @param bool $signed
146
+     * @param string $wrapper stream wrapper class
147
+     * @return resource
148
+     *
149
+     * @throws \BadMethodCallException
150
+     */
151
+    public static function wrap($source, $internalPath, $fullPath, array $header,
152
+                                $uid,
153
+                                \OCP\Encryption\IEncryptionModule $encryptionModule,
154
+                                \OC\Files\Storage\Storage $storage,
155
+                                \OC\Files\Storage\Wrapper\Encryption $encStorage,
156
+                                \OC\Encryption\Util $util,
157
+                                    \OC\Encryption\File $file,
158
+                                $mode,
159
+                                $size,
160
+                                $unencryptedSize,
161
+                                $headerSize,
162
+                                $signed,
163
+                                $wrapper = Encryption::class) {
164
+
165
+        $context = stream_context_create(array(
166
+            'ocencryption' => array(
167
+                'source' => $source,
168
+                'storage' => $storage,
169
+                'internalPath' => $internalPath,
170
+                'fullPath' => $fullPath,
171
+                'encryptionModule' => $encryptionModule,
172
+                'header' => $header,
173
+                'uid' => $uid,
174
+                'util' => $util,
175
+                'file' => $file,
176
+                'size' => $size,
177
+                'unencryptedSize' => $unencryptedSize,
178
+                'encryptionStorage' => $encStorage,
179
+                'headerSize' => $headerSize,
180
+                'signed' => $signed
181
+            )
182
+        ));
183
+
184
+        return self::wrapSource($source, $context, 'ocencryption', $wrapper, $mode);
185
+    }
186
+
187
+    /**
188
+     * add stream wrapper
189
+     *
190
+     * @param resource $source
191
+     * @param string $mode
192
+     * @param resource $context
193
+     * @param string $protocol
194
+     * @param string $class
195
+     * @return resource
196
+     * @throws \BadMethodCallException
197
+     */
198
+    protected static function wrapSource($source, $context, $protocol, $class, $mode = 'r+') {
199
+        try {
200
+            stream_wrapper_register($protocol, $class);
201
+            if (self::isDirectoryHandle($source)) {
202
+                $wrapped = opendir($protocol . '://', $context);
203
+            } else {
204
+                $wrapped = fopen($protocol . '://', $mode, false, $context);
205
+            }
206
+        } catch (\BadMethodCallException $e) {
207
+            stream_wrapper_unregister($protocol);
208
+            throw $e;
209
+        }
210
+        stream_wrapper_unregister($protocol);
211
+        return $wrapped;
212
+    }
213
+
214
+    /**
215
+     * Load the source from the stream context and return the context options
216
+     *
217
+     * @param string $name
218
+     * @return array
219
+     * @throws \BadMethodCallException
220
+     */
221
+    protected function loadContext($name) {
222
+        $context = parent::loadContext($name);
223
+
224
+        foreach ($this->expectedContextProperties as $property) {
225
+            if (array_key_exists($property, $context)) {
226
+                $this->{$property} = $context[$property];
227
+            } else {
228
+                throw new \BadMethodCallException('Invalid context, "' . $property . '" options not set');
229
+            }
230
+        }
231
+        return $context;
232
+
233
+    }
234
+
235
+    public function stream_open($path, $mode, $options, &$opened_path) {
236
+        $this->loadContext('ocencryption');
237
+
238
+        $this->position = 0;
239
+        $this->cache = '';
240
+        $this->writeFlag = false;
241
+        $this->fileUpdated = false;
242
+        $this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed);
243
+
244
+        if (
245
+            $mode === 'w'
246
+            || $mode === 'w+'
247
+            || $mode === 'wb'
248
+            || $mode === 'wb+'
249
+            || $mode === 'r+'
250
+            || $mode === 'rb+'
251
+        ) {
252
+            $this->readOnly = false;
253
+        } else {
254
+            $this->readOnly = true;
255
+        }
256
+
257
+        $sharePath = $this->fullPath;
258
+        if (!$this->storage->file_exists($this->internalPath)) {
259
+            $sharePath = dirname($sharePath);
260
+        }
261
+
262
+        $accessList = [];
263
+        if ($this->encryptionModule->needDetailedAccessList()) {
264
+            $accessList = $this->file->getAccessList($sharePath);
265
+        }
266
+        $this->newHeader = $this->encryptionModule->begin($this->fullPath, $this->uid, $mode, $this->header, $accessList);
267
+
268
+        if (
269
+            $mode === 'w'
270
+            || $mode === 'w+'
271
+            || $mode === 'wb'
272
+            || $mode === 'wb+'
273
+        ) {
274
+            // We're writing a new file so start write counter with 0 bytes
275
+            $this->unencryptedSize = 0;
276
+            $this->writeHeader();
277
+            $this->headerSize = $this->util->getHeaderSize();
278
+            $this->size = $this->headerSize;
279
+        } else {
280
+            $this->skipHeader();
281
+        }
282
+
283
+        return true;
284
+
285
+    }
286
+
287
+    public function stream_eof() {
288
+        return $this->position >= $this->unencryptedSize;
289
+    }
290
+
291
+    public function stream_read($count) {
292
+
293
+        $result = '';
294
+
295
+        $count = min($count, $this->unencryptedSize - $this->position);
296
+        while ($count > 0) {
297
+            $remainingLength = $count;
298
+            // update the cache of the current block
299
+            $this->readCache();
300
+            // determine the relative position in the current block
301
+            $blockPosition = ($this->position % $this->unencryptedBlockSize);
302
+            // if entire read inside current block then only position needs to be updated
303
+            if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
304
+                $result .= substr($this->cache, $blockPosition, $remainingLength);
305
+                $this->position += $remainingLength;
306
+                $count = 0;
307
+                // otherwise remainder of current block is fetched, the block is flushed and the position updated
308
+            } else {
309
+                $result .= substr($this->cache, $blockPosition);
310
+                $this->flush();
311
+                $this->position += ($this->unencryptedBlockSize - $blockPosition);
312
+                $count -= ($this->unencryptedBlockSize - $blockPosition);
313
+            }
314
+        }
315
+        return $result;
316
+
317
+    }
318
+
319
+    public function stream_write($data) {
320
+        $length = 0;
321
+        // loop over $data to fit it in 6126 sized unencrypted blocks
322
+        while (isset($data[0])) {
323
+            $remainingLength = strlen($data);
324
+
325
+            // set the cache to the current 6126 block
326
+            $this->readCache();
327
+
328
+            // for seekable streams the pointer is moved back to the beginning of the encrypted block
329
+            // flush will start writing there when the position moves to another block
330
+            $positionInFile = (int)floor($this->position / $this->unencryptedBlockSize) *
331
+                $this->util->getBlockSize() + $this->headerSize;
332
+            $resultFseek = $this->parentStreamSeek($positionInFile);
333
+
334
+            // only allow writes on seekable streams, or at the end of the encrypted stream
335
+            if (!$this->readOnly && ($resultFseek || $positionInFile === $this->size)) {
336
+
337
+                // switch the writeFlag so flush() will write the block
338
+                $this->writeFlag = true;
339
+                $this->fileUpdated = true;
340
+
341
+                // determine the relative position in the current block
342
+                $blockPosition = ($this->position % $this->unencryptedBlockSize);
343
+                // check if $data fits in current block
344
+                // if so, overwrite existing data (if any)
345
+                // update position and liberate $data
346
+                if ($remainingLength < ($this->unencryptedBlockSize - $blockPosition)) {
347
+                    $this->cache = substr($this->cache, 0, $blockPosition)
348
+                        . $data . substr($this->cache, $blockPosition + $remainingLength);
349
+                    $this->position += $remainingLength;
350
+                    $length += $remainingLength;
351
+                    $data = '';
352
+                    // if $data doesn't fit the current block, the fill the current block and reiterate
353
+                    // after the block is filled, it is flushed and $data is updatedxxx
354
+                } else {
355
+                    $this->cache = substr($this->cache, 0, $blockPosition) .
356
+                        substr($data, 0, $this->unencryptedBlockSize - $blockPosition);
357
+                    $this->flush();
358
+                    $this->position += ($this->unencryptedBlockSize - $blockPosition);
359
+                    $length += ($this->unencryptedBlockSize - $blockPosition);
360
+                    $data = substr($data, $this->unencryptedBlockSize - $blockPosition);
361
+                }
362
+            } else {
363
+                $data = '';
364
+            }
365
+            $this->unencryptedSize = max($this->unencryptedSize, $this->position);
366
+        }
367
+        return $length;
368
+    }
369
+
370
+    public function stream_tell() {
371
+        return $this->position;
372
+    }
373
+
374
+    public function stream_seek($offset, $whence = SEEK_SET) {
375
+
376
+        $return = false;
377
+
378
+        switch ($whence) {
379
+            case SEEK_SET:
380
+                $newPosition = $offset;
381
+                break;
382
+            case SEEK_CUR:
383
+                $newPosition = $this->position + $offset;
384
+                break;
385
+            case SEEK_END:
386
+                $newPosition = $this->unencryptedSize + $offset;
387
+                break;
388
+            default:
389
+                return $return;
390
+        }
391
+
392
+        if ($newPosition > $this->unencryptedSize || $newPosition < 0) {
393
+            return $return;
394
+        }
395
+
396
+        $newFilePosition = floor($newPosition / $this->unencryptedBlockSize)
397
+            * $this->util->getBlockSize() + $this->headerSize;
398
+
399
+        $oldFilePosition = parent::stream_tell();
400
+        if ($this->parentStreamSeek($newFilePosition)) {
401
+            $this->parentStreamSeek($oldFilePosition);
402
+            $this->flush();
403
+            $this->parentStreamSeek($newFilePosition);
404
+            $this->position = $newPosition;
405
+            $return = true;
406
+        }
407
+        return $return;
408
+
409
+    }
410
+
411
+    public function stream_close() {
412
+        $this->flush('end');
413
+        $position = (int)floor($this->position/$this->unencryptedBlockSize);
414
+        $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
415
+        if ($this->readOnly === false) {
416
+            if(!empty($remainingData)) {
417
+                parent::stream_write($remainingData);
418
+            }
419
+            $this->encryptionStorage->updateUnencryptedSize($this->fullPath, $this->unencryptedSize);
420
+        }
421
+        $result = parent::stream_close();
422
+
423
+        if ($this->fileUpdated) {
424
+            $cache = $this->storage->getCache();
425
+            $cacheEntry = $cache->get($this->internalPath);
426
+            if ($cacheEntry) {
427
+                $version = $cacheEntry['encryptedVersion'] + 1;
428
+                $cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
429
+            }
430
+        }
431
+
432
+        return $result;
433
+    }
434
+
435
+    /**
436
+     * write block to file
437
+     * @param string $positionPrefix
438
+     */
439
+    protected function flush($positionPrefix = '') {
440
+        // write to disk only when writeFlag was set to 1
441
+        if ($this->writeFlag) {
442
+            // Disable the file proxies so that encryption is not
443
+            // automatically attempted when the file is written to disk -
444
+            // we are handling that separately here and we don't want to
445
+            // get into an infinite loop
446
+            $position = (int)floor($this->position/$this->unencryptedBlockSize);
447
+            $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
448
+            $bytesWritten = parent::stream_write($encrypted);
449
+            $this->writeFlag = false;
450
+            // Check whether the write concerns the last block
451
+            // If so then update the encrypted filesize
452
+            // Note that the unencrypted pointer and filesize are NOT yet updated when flush() is called
453
+            // We recalculate the encrypted filesize as we do not know the context of calling flush()
454
+            $completeBlocksInFile=(int)floor($this->unencryptedSize/$this->unencryptedBlockSize);
455
+            if ($completeBlocksInFile === (int)floor($this->position/$this->unencryptedBlockSize)) {
456
+                $this->size = $this->util->getBlockSize() * $completeBlocksInFile;
457
+                $this->size += $bytesWritten;
458
+                $this->size += $this->headerSize;
459
+            }
460
+        }
461
+        // always empty the cache (otherwise readCache() will not fill it with the new block)
462
+        $this->cache = '';
463
+    }
464
+
465
+    /**
466
+     * read block to file
467
+     */
468
+    protected function readCache() {
469
+        // cache should always be empty string when this function is called
470
+        // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block
471
+        if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
472
+            // Get the data from the file handle
473
+            $data = parent::stream_read($this->util->getBlockSize());
474
+            $position = (int)floor($this->position/$this->unencryptedBlockSize);
475
+            $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
476
+            if($numberOfChunks === $position) {
477
+                $position .= 'end';
478
+            }
479
+            $this->cache = $this->encryptionModule->decrypt($data, $position);
480
+        }
481
+    }
482
+
483
+    /**
484
+     * write header at beginning of encrypted file
485
+     *
486
+     * @return integer
487
+     * @throws EncryptionHeaderKeyExistsException if header key is already in use
488
+     */
489
+    protected function writeHeader() {
490
+        $header = $this->util->createHeader($this->newHeader, $this->encryptionModule);
491
+        return parent::stream_write($header);
492
+    }
493
+
494
+    /**
495
+     * read first block to skip the header
496
+     */
497
+    protected function skipHeader() {
498
+        parent::stream_read($this->headerSize);
499
+    }
500
+
501
+    /**
502
+     * call stream_seek() from parent class
503
+     *
504
+     * @param integer $position
505
+     * @return bool
506
+     */
507
+    protected function parentStreamSeek($position) {
508
+        return parent::stream_seek($position);
509
+    }
510
+
511
+    /**
512
+     * @param string $path
513
+     * @param array $options
514
+     * @return bool
515
+     */
516
+    public function dir_opendir($path, $options) {
517
+        return false;
518
+    }
519 519
 
520 520
 }
Please login to merge, or discard this patch.
apps/encryption/lib/Crypto/Encryption.php 1 patch
Indentation   +553 added lines, -553 removed lines patch added patch discarded remove patch
@@ -45,557 +45,557 @@
 block discarded – undo
45 45
 
46 46
 class Encryption implements IEncryptionModule {
47 47
 
48
-	const ID = 'OC_DEFAULT_MODULE';
49
-	const DISPLAY_NAME = 'Default encryption module';
50
-
51
-	/**
52
-	 * @var Crypt
53
-	 */
54
-	private $crypt;
55
-
56
-	/** @var string */
57
-	private $cipher;
58
-
59
-	/** @var string */
60
-	private $path;
61
-
62
-	/** @var string */
63
-	private $user;
64
-
65
-	/** @var  array */
66
-	private $owner;
67
-
68
-	/** @var string */
69
-	private $fileKey;
70
-
71
-	/** @var string */
72
-	private $writeCache;
73
-
74
-	/** @var KeyManager */
75
-	private $keyManager;
76
-
77
-	/** @var array */
78
-	private $accessList;
79
-
80
-	/** @var boolean */
81
-	private $isWriteOperation;
82
-
83
-	/** @var Util */
84
-	private $util;
85
-
86
-	/** @var  Session */
87
-	private $session;
88
-
89
-	/** @var  ILogger */
90
-	private $logger;
91
-
92
-	/** @var IL10N */
93
-	private $l;
94
-
95
-	/** @var EncryptAll */
96
-	private $encryptAll;
97
-
98
-	/** @var  bool */
99
-	private $useMasterPassword;
100
-
101
-	/** @var DecryptAll  */
102
-	private $decryptAll;
103
-
104
-	/** @var int unencrypted block size if block contains signature */
105
-	private $unencryptedBlockSizeSigned = 6072;
106
-
107
-	/** @var int unencrypted block size */
108
-	private $unencryptedBlockSize = 6126;
109
-
110
-	/** @var int Current version of the file */
111
-	private $version = 0;
112
-
113
-	/** @var array remember encryption signature version */
114
-	private static $rememberVersion = [];
115
-
116
-
117
-	/**
118
-	 *
119
-	 * @param Crypt $crypt
120
-	 * @param KeyManager $keyManager
121
-	 * @param Util $util
122
-	 * @param Session $session
123
-	 * @param EncryptAll $encryptAll
124
-	 * @param DecryptAll $decryptAll
125
-	 * @param ILogger $logger
126
-	 * @param IL10N $il10n
127
-	 */
128
-	public function __construct(Crypt $crypt,
129
-								KeyManager $keyManager,
130
-								Util $util,
131
-								Session $session,
132
-								EncryptAll $encryptAll,
133
-								DecryptAll $decryptAll,
134
-								ILogger $logger,
135
-								IL10N $il10n) {
136
-		$this->crypt = $crypt;
137
-		$this->keyManager = $keyManager;
138
-		$this->util = $util;
139
-		$this->session = $session;
140
-		$this->encryptAll = $encryptAll;
141
-		$this->decryptAll = $decryptAll;
142
-		$this->logger = $logger;
143
-		$this->l = $il10n;
144
-		$this->owner = [];
145
-		$this->useMasterPassword = $util->isMasterKeyEnabled();
146
-	}
147
-
148
-	/**
149
-	 * @return string defining the technical unique id
150
-	 */
151
-	public function getId() {
152
-		return self::ID;
153
-	}
154
-
155
-	/**
156
-	 * In comparison to getKey() this function returns a human readable (maybe translated) name
157
-	 *
158
-	 * @return string
159
-	 */
160
-	public function getDisplayName() {
161
-		return self::DISPLAY_NAME;
162
-	}
163
-
164
-	/**
165
-	 * start receiving chunks from a file. This is the place where you can
166
-	 * perform some initial step before starting encrypting/decrypting the
167
-	 * chunks
168
-	 *
169
-	 * @param string $path to the file
170
-	 * @param string $user who read/write the file
171
-	 * @param string $mode php stream open mode
172
-	 * @param array $header contains the header data read from the file
173
-	 * @param array $accessList who has access to the file contains the key 'users' and 'public'
174
-	 *
175
-	 * @return array $header contain data as key-value pairs which should be
176
-	 *                       written to the header, in case of a write operation
177
-	 *                       or if no additional data is needed return a empty array
178
-	 */
179
-	public function begin($path, $user, $mode, array $header, array $accessList) {
180
-		$this->path = $this->getPathToRealFile($path);
181
-		$this->accessList = $accessList;
182
-		$this->user = $user;
183
-		$this->isWriteOperation = false;
184
-		$this->writeCache = '';
185
-
186
-		if($this->session->isReady() === false) {
187
-			// if the master key is enabled we can initialize encryption
188
-			// with a empty password and user name
189
-			if ($this->util->isMasterKeyEnabled()) {
190
-				$this->keyManager->init('', '');
191
-			}
192
-		}
193
-
194
-		if ($this->session->decryptAllModeActivated()) {
195
-			$encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
196
-			$shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
197
-			$this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
198
-				$shareKey,
199
-				$this->session->getDecryptAllKey());
200
-		} else {
201
-			$this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
202
-		}
203
-
204
-		// always use the version from the original file, also part files
205
-		// need to have a correct version number if they get moved over to the
206
-		// final location
207
-		$this->version = (int)$this->keyManager->getVersion($this->stripPartFileExtension($path), new View());
208
-
209
-		if (
210
-			$mode === 'w'
211
-			|| $mode === 'w+'
212
-			|| $mode === 'wb'
213
-			|| $mode === 'wb+'
214
-		) {
215
-			$this->isWriteOperation = true;
216
-			if (empty($this->fileKey)) {
217
-				$this->fileKey = $this->crypt->generateFileKey();
218
-			}
219
-		} else {
220
-			// if we read a part file we need to increase the version by 1
221
-			// because the version number was also increased by writing
222
-			// the part file
223
-			if(Scanner::isPartialFile($path)) {
224
-				$this->version = $this->version + 1;
225
-			}
226
-		}
227
-
228
-		if ($this->isWriteOperation) {
229
-			$this->cipher = $this->crypt->getCipher();
230
-		} elseif (isset($header['cipher'])) {
231
-			$this->cipher = $header['cipher'];
232
-		} else {
233
-			// if we read a file without a header we fall-back to the legacy cipher
234
-			// which was used in <=oC6
235
-			$this->cipher = $this->crypt->getLegacyCipher();
236
-		}
237
-
238
-		return array('cipher' => $this->cipher, 'signed' => 'true');
239
-	}
240
-
241
-	/**
242
-	 * last chunk received. This is the place where you can perform some final
243
-	 * operation and return some remaining data if something is left in your
244
-	 * buffer.
245
-	 *
246
-	 * @param string $path to the file
247
-	 * @param int $position
248
-	 * @return string remained data which should be written to the file in case
249
-	 *                of a write operation
250
-	 * @throws PublicKeyMissingException
251
-	 * @throws \Exception
252
-	 * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
253
-	 */
254
-	public function end($path, $position = 0) {
255
-		$result = '';
256
-		if ($this->isWriteOperation) {
257
-			// in case of a part file we remember the new signature versions
258
-			// the version will be set later on update.
259
-			// This way we make sure that other apps listening to the pre-hooks
260
-			// still get the old version which should be the correct value for them
261
-			if (Scanner::isPartialFile($path)) {
262
-				self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1;
263
-			}
264
-			if (!empty($this->writeCache)) {
265
-				$result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position);
266
-				$this->writeCache = '';
267
-			}
268
-			$publicKeys = array();
269
-			if ($this->useMasterPassword === true) {
270
-				$publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
271
-			} else {
272
-				foreach ($this->accessList['users'] as $uid) {
273
-					try {
274
-						$publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
275
-					} catch (PublicKeyMissingException $e) {
276
-						$this->logger->warning(
277
-							'no public key found for user "{uid}", user will not be able to read the file',
278
-							['app' => 'encryption', 'uid' => $uid]
279
-						);
280
-						// if the public key of the owner is missing we should fail
281
-						if ($uid === $this->user) {
282
-							throw $e;
283
-						}
284
-					}
285
-				}
286
-			}
287
-
288
-			$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
289
-			$encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
290
-			$this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
291
-		}
292
-		return $result;
293
-	}
294
-
295
-
296
-
297
-	/**
298
-	 * encrypt data
299
-	 *
300
-	 * @param string $data you want to encrypt
301
-	 * @param int $position
302
-	 * @return string encrypted data
303
-	 */
304
-	public function encrypt($data, $position = 0) {
305
-		// If extra data is left over from the last round, make sure it
306
-		// is integrated into the next block
307
-		if ($this->writeCache) {
308
-
309
-			// Concat writeCache to start of $data
310
-			$data = $this->writeCache . $data;
311
-
312
-			// Clear the write cache, ready for reuse - it has been
313
-			// flushed and its old contents processed
314
-			$this->writeCache = '';
315
-
316
-		}
317
-
318
-		$encrypted = '';
319
-		// While there still remains some data to be processed & written
320
-		while (strlen($data) > 0) {
321
-
322
-			// Remaining length for this iteration, not of the
323
-			// entire file (may be greater than 8192 bytes)
324
-			$remainingLength = strlen($data);
325
-
326
-			// If data remaining to be written is less than the
327
-			// size of 1 6126 byte block
328
-			if ($remainingLength < $this->unencryptedBlockSizeSigned) {
329
-
330
-				// Set writeCache to contents of $data
331
-				// The writeCache will be carried over to the
332
-				// next write round, and added to the start of
333
-				// $data to ensure that written blocks are
334
-				// always the correct length. If there is still
335
-				// data in writeCache after the writing round
336
-				// has finished, then the data will be written
337
-				// to disk by $this->flush().
338
-				$this->writeCache = $data;
339
-
340
-				// Clear $data ready for next round
341
-				$data = '';
342
-
343
-			} else {
344
-
345
-				// Read the chunk from the start of $data
346
-				$chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
347
-
348
-				$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
349
-
350
-				// Remove the chunk we just processed from
351
-				// $data, leaving only unprocessed data in $data
352
-				// var, for handling on the next round
353
-				$data = substr($data, $this->unencryptedBlockSizeSigned);
354
-
355
-			}
356
-
357
-		}
358
-
359
-		return $encrypted;
360
-	}
361
-
362
-	/**
363
-	 * decrypt data
364
-	 *
365
-	 * @param string $data you want to decrypt
366
-	 * @param int $position
367
-	 * @return string decrypted data
368
-	 * @throws DecryptionFailedException
369
-	 */
370
-	public function decrypt($data, $position = 0) {
371
-		if (empty($this->fileKey)) {
372
-			$msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
373
-			$hint = $this->l->t('Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
374
-			$this->logger->error($msg);
375
-
376
-			throw new DecryptionFailedException($msg, $hint);
377
-		}
378
-
379
-		return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
380
-	}
381
-
382
-	/**
383
-	 * update encrypted file, e.g. give additional users access to the file
384
-	 *
385
-	 * @param string $path path to the file which should be updated
386
-	 * @param string $uid of the user who performs the operation
387
-	 * @param array $accessList who has access to the file contains the key 'users' and 'public'
388
-	 * @return boolean
389
-	 */
390
-	public function update($path, $uid, array $accessList) {
391
-
392
-		if (empty($accessList)) {
393
-			if (isset(self::$rememberVersion[$path])) {
394
-				$this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
395
-				unset(self::$rememberVersion[$path]);
396
-			}
397
-			return;
398
-		}
399
-
400
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
401
-
402
-		if (!empty($fileKey)) {
403
-
404
-			$publicKeys = array();
405
-			if ($this->useMasterPassword === true) {
406
-				$publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
407
-			} else {
408
-				foreach ($accessList['users'] as $user) {
409
-					try {
410
-						$publicKeys[$user] = $this->keyManager->getPublicKey($user);
411
-					} catch (PublicKeyMissingException $e) {
412
-						$this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
413
-					}
414
-				}
415
-			}
416
-
417
-			$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
418
-
419
-			$encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
420
-
421
-			$this->keyManager->deleteAllFileKeys($path);
422
-
423
-			$this->keyManager->setAllFileKeys($path, $encryptedFileKey);
424
-
425
-		} else {
426
-			$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
427
-				array('file' => $path, 'app' => 'encryption'));
428
-
429
-			return false;
430
-		}
431
-
432
-		return true;
433
-	}
434
-
435
-	/**
436
-	 * should the file be encrypted or not
437
-	 *
438
-	 * @param string $path
439
-	 * @return boolean
440
-	 */
441
-	public function shouldEncrypt($path) {
442
-		if ($this->util->shouldEncryptHomeStorage() === false) {
443
-			$storage = $this->util->getStorage($path);
444
-			if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
445
-				return false;
446
-			}
447
-		}
448
-		$parts = explode('/', $path);
449
-		if (count($parts) < 4) {
450
-			return false;
451
-		}
452
-
453
-		if ($parts[2] === 'files') {
454
-			return true;
455
-		}
456
-		if ($parts[2] === 'files_versions') {
457
-			return true;
458
-		}
459
-		if ($parts[2] === 'files_trashbin') {
460
-			return true;
461
-		}
462
-
463
-		return false;
464
-	}
465
-
466
-	/**
467
-	 * get size of the unencrypted payload per block.
468
-	 * Nextcloud read/write files with a block size of 8192 byte
469
-	 *
470
-	 * @param bool $signed
471
-	 * @return int
472
-	 */
473
-	public function getUnencryptedBlockSize($signed = false) {
474
-		if ($signed === false) {
475
-			return $this->unencryptedBlockSize;
476
-		}
477
-
478
-		return $this->unencryptedBlockSizeSigned;
479
-	}
480
-
481
-	/**
482
-	 * check if the encryption module is able to read the file,
483
-	 * e.g. if all encryption keys exists
484
-	 *
485
-	 * @param string $path
486
-	 * @param string $uid user for whom we want to check if he can read the file
487
-	 * @return bool
488
-	 * @throws DecryptionFailedException
489
-	 */
490
-	public function isReadable($path, $uid) {
491
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
492
-		if (empty($fileKey)) {
493
-			$owner = $this->util->getOwner($path);
494
-			if ($owner !== $uid) {
495
-				// if it is a shared file we throw a exception with a useful
496
-				// error message because in this case it means that the file was
497
-				// shared with the user at a point where the user didn't had a
498
-				// valid private/public key
499
-				$msg = 'Encryption module "' . $this->getDisplayName() .
500
-					'" is not able to read ' . $path;
501
-				$hint = $this->l->t('Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
502
-				$this->logger->warning($msg);
503
-				throw new DecryptionFailedException($msg, $hint);
504
-			}
505
-			return false;
506
-		}
507
-
508
-		return true;
509
-	}
510
-
511
-	/**
512
-	 * Initial encryption of all files
513
-	 *
514
-	 * @param InputInterface $input
515
-	 * @param OutputInterface $output write some status information to the terminal during encryption
516
-	 */
517
-	public function encryptAll(InputInterface $input, OutputInterface $output) {
518
-		$this->encryptAll->encryptAll($input, $output);
519
-	}
520
-
521
-	/**
522
-	 * prepare module to perform decrypt all operation
523
-	 *
524
-	 * @param InputInterface $input
525
-	 * @param OutputInterface $output
526
-	 * @param string $user
527
-	 * @return bool
528
-	 */
529
-	public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
530
-		return $this->decryptAll->prepare($input, $output, $user);
531
-	}
532
-
533
-
534
-	/**
535
-	 * @param string $path
536
-	 * @return string
537
-	 */
538
-	protected function getPathToRealFile($path) {
539
-		$realPath = $path;
540
-		$parts = explode('/', $path);
541
-		if ($parts[2] === 'files_versions') {
542
-			$realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
543
-			$length = strrpos($realPath, '.');
544
-			$realPath = substr($realPath, 0, $length);
545
-		}
546
-
547
-		return $realPath;
548
-	}
549
-
550
-	/**
551
-	 * remove .part file extension and the ocTransferId from the file to get the
552
-	 * original file name
553
-	 *
554
-	 * @param string $path
555
-	 * @return string
556
-	 */
557
-	protected function stripPartFileExtension($path) {
558
-		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
559
-			$pos = strrpos($path, '.', -6);
560
-			$path = substr($path, 0, $pos);
561
-		}
562
-
563
-		return $path;
564
-	}
565
-
566
-	/**
567
-	 * get owner of a file
568
-	 *
569
-	 * @param string $path
570
-	 * @return string
571
-	 */
572
-	protected function getOwner($path) {
573
-		if (!isset($this->owner[$path])) {
574
-			$this->owner[$path] = $this->util->getOwner($path);
575
-		}
576
-		return $this->owner[$path];
577
-	}
578
-
579
-	/**
580
-	 * Check if the module is ready to be used by that specific user.
581
-	 * In case a module is not ready - because e.g. key pairs have not been generated
582
-	 * upon login this method can return false before any operation starts and might
583
-	 * cause issues during operations.
584
-	 *
585
-	 * @param string $user
586
-	 * @return boolean
587
-	 * @since 9.1.0
588
-	 */
589
-	public function isReadyForUser($user) {
590
-		return $this->keyManager->userHasKeys($user);
591
-	}
592
-
593
-	/**
594
-	 * We only need a detailed access list if the master key is not enabled
595
-	 *
596
-	 * @return bool
597
-	 */
598
-	public function needDetailedAccessList() {
599
-		return !$this->util->isMasterKeyEnabled();
600
-	}
48
+    const ID = 'OC_DEFAULT_MODULE';
49
+    const DISPLAY_NAME = 'Default encryption module';
50
+
51
+    /**
52
+     * @var Crypt
53
+     */
54
+    private $crypt;
55
+
56
+    /** @var string */
57
+    private $cipher;
58
+
59
+    /** @var string */
60
+    private $path;
61
+
62
+    /** @var string */
63
+    private $user;
64
+
65
+    /** @var  array */
66
+    private $owner;
67
+
68
+    /** @var string */
69
+    private $fileKey;
70
+
71
+    /** @var string */
72
+    private $writeCache;
73
+
74
+    /** @var KeyManager */
75
+    private $keyManager;
76
+
77
+    /** @var array */
78
+    private $accessList;
79
+
80
+    /** @var boolean */
81
+    private $isWriteOperation;
82
+
83
+    /** @var Util */
84
+    private $util;
85
+
86
+    /** @var  Session */
87
+    private $session;
88
+
89
+    /** @var  ILogger */
90
+    private $logger;
91
+
92
+    /** @var IL10N */
93
+    private $l;
94
+
95
+    /** @var EncryptAll */
96
+    private $encryptAll;
97
+
98
+    /** @var  bool */
99
+    private $useMasterPassword;
100
+
101
+    /** @var DecryptAll  */
102
+    private $decryptAll;
103
+
104
+    /** @var int unencrypted block size if block contains signature */
105
+    private $unencryptedBlockSizeSigned = 6072;
106
+
107
+    /** @var int unencrypted block size */
108
+    private $unencryptedBlockSize = 6126;
109
+
110
+    /** @var int Current version of the file */
111
+    private $version = 0;
112
+
113
+    /** @var array remember encryption signature version */
114
+    private static $rememberVersion = [];
115
+
116
+
117
+    /**
118
+     *
119
+     * @param Crypt $crypt
120
+     * @param KeyManager $keyManager
121
+     * @param Util $util
122
+     * @param Session $session
123
+     * @param EncryptAll $encryptAll
124
+     * @param DecryptAll $decryptAll
125
+     * @param ILogger $logger
126
+     * @param IL10N $il10n
127
+     */
128
+    public function __construct(Crypt $crypt,
129
+                                KeyManager $keyManager,
130
+                                Util $util,
131
+                                Session $session,
132
+                                EncryptAll $encryptAll,
133
+                                DecryptAll $decryptAll,
134
+                                ILogger $logger,
135
+                                IL10N $il10n) {
136
+        $this->crypt = $crypt;
137
+        $this->keyManager = $keyManager;
138
+        $this->util = $util;
139
+        $this->session = $session;
140
+        $this->encryptAll = $encryptAll;
141
+        $this->decryptAll = $decryptAll;
142
+        $this->logger = $logger;
143
+        $this->l = $il10n;
144
+        $this->owner = [];
145
+        $this->useMasterPassword = $util->isMasterKeyEnabled();
146
+    }
147
+
148
+    /**
149
+     * @return string defining the technical unique id
150
+     */
151
+    public function getId() {
152
+        return self::ID;
153
+    }
154
+
155
+    /**
156
+     * In comparison to getKey() this function returns a human readable (maybe translated) name
157
+     *
158
+     * @return string
159
+     */
160
+    public function getDisplayName() {
161
+        return self::DISPLAY_NAME;
162
+    }
163
+
164
+    /**
165
+     * start receiving chunks from a file. This is the place where you can
166
+     * perform some initial step before starting encrypting/decrypting the
167
+     * chunks
168
+     *
169
+     * @param string $path to the file
170
+     * @param string $user who read/write the file
171
+     * @param string $mode php stream open mode
172
+     * @param array $header contains the header data read from the file
173
+     * @param array $accessList who has access to the file contains the key 'users' and 'public'
174
+     *
175
+     * @return array $header contain data as key-value pairs which should be
176
+     *                       written to the header, in case of a write operation
177
+     *                       or if no additional data is needed return a empty array
178
+     */
179
+    public function begin($path, $user, $mode, array $header, array $accessList) {
180
+        $this->path = $this->getPathToRealFile($path);
181
+        $this->accessList = $accessList;
182
+        $this->user = $user;
183
+        $this->isWriteOperation = false;
184
+        $this->writeCache = '';
185
+
186
+        if($this->session->isReady() === false) {
187
+            // if the master key is enabled we can initialize encryption
188
+            // with a empty password and user name
189
+            if ($this->util->isMasterKeyEnabled()) {
190
+                $this->keyManager->init('', '');
191
+            }
192
+        }
193
+
194
+        if ($this->session->decryptAllModeActivated()) {
195
+            $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
196
+            $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
197
+            $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
198
+                $shareKey,
199
+                $this->session->getDecryptAllKey());
200
+        } else {
201
+            $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
202
+        }
203
+
204
+        // always use the version from the original file, also part files
205
+        // need to have a correct version number if they get moved over to the
206
+        // final location
207
+        $this->version = (int)$this->keyManager->getVersion($this->stripPartFileExtension($path), new View());
208
+
209
+        if (
210
+            $mode === 'w'
211
+            || $mode === 'w+'
212
+            || $mode === 'wb'
213
+            || $mode === 'wb+'
214
+        ) {
215
+            $this->isWriteOperation = true;
216
+            if (empty($this->fileKey)) {
217
+                $this->fileKey = $this->crypt->generateFileKey();
218
+            }
219
+        } else {
220
+            // if we read a part file we need to increase the version by 1
221
+            // because the version number was also increased by writing
222
+            // the part file
223
+            if(Scanner::isPartialFile($path)) {
224
+                $this->version = $this->version + 1;
225
+            }
226
+        }
227
+
228
+        if ($this->isWriteOperation) {
229
+            $this->cipher = $this->crypt->getCipher();
230
+        } elseif (isset($header['cipher'])) {
231
+            $this->cipher = $header['cipher'];
232
+        } else {
233
+            // if we read a file without a header we fall-back to the legacy cipher
234
+            // which was used in <=oC6
235
+            $this->cipher = $this->crypt->getLegacyCipher();
236
+        }
237
+
238
+        return array('cipher' => $this->cipher, 'signed' => 'true');
239
+    }
240
+
241
+    /**
242
+     * last chunk received. This is the place where you can perform some final
243
+     * operation and return some remaining data if something is left in your
244
+     * buffer.
245
+     *
246
+     * @param string $path to the file
247
+     * @param int $position
248
+     * @return string remained data which should be written to the file in case
249
+     *                of a write operation
250
+     * @throws PublicKeyMissingException
251
+     * @throws \Exception
252
+     * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
253
+     */
254
+    public function end($path, $position = 0) {
255
+        $result = '';
256
+        if ($this->isWriteOperation) {
257
+            // in case of a part file we remember the new signature versions
258
+            // the version will be set later on update.
259
+            // This way we make sure that other apps listening to the pre-hooks
260
+            // still get the old version which should be the correct value for them
261
+            if (Scanner::isPartialFile($path)) {
262
+                self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1;
263
+            }
264
+            if (!empty($this->writeCache)) {
265
+                $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position);
266
+                $this->writeCache = '';
267
+            }
268
+            $publicKeys = array();
269
+            if ($this->useMasterPassword === true) {
270
+                $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
271
+            } else {
272
+                foreach ($this->accessList['users'] as $uid) {
273
+                    try {
274
+                        $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
275
+                    } catch (PublicKeyMissingException $e) {
276
+                        $this->logger->warning(
277
+                            'no public key found for user "{uid}", user will not be able to read the file',
278
+                            ['app' => 'encryption', 'uid' => $uid]
279
+                        );
280
+                        // if the public key of the owner is missing we should fail
281
+                        if ($uid === $this->user) {
282
+                            throw $e;
283
+                        }
284
+                    }
285
+                }
286
+            }
287
+
288
+            $publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
289
+            $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
290
+            $this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
291
+        }
292
+        return $result;
293
+    }
294
+
295
+
296
+
297
+    /**
298
+     * encrypt data
299
+     *
300
+     * @param string $data you want to encrypt
301
+     * @param int $position
302
+     * @return string encrypted data
303
+     */
304
+    public function encrypt($data, $position = 0) {
305
+        // If extra data is left over from the last round, make sure it
306
+        // is integrated into the next block
307
+        if ($this->writeCache) {
308
+
309
+            // Concat writeCache to start of $data
310
+            $data = $this->writeCache . $data;
311
+
312
+            // Clear the write cache, ready for reuse - it has been
313
+            // flushed and its old contents processed
314
+            $this->writeCache = '';
315
+
316
+        }
317
+
318
+        $encrypted = '';
319
+        // While there still remains some data to be processed & written
320
+        while (strlen($data) > 0) {
321
+
322
+            // Remaining length for this iteration, not of the
323
+            // entire file (may be greater than 8192 bytes)
324
+            $remainingLength = strlen($data);
325
+
326
+            // If data remaining to be written is less than the
327
+            // size of 1 6126 byte block
328
+            if ($remainingLength < $this->unencryptedBlockSizeSigned) {
329
+
330
+                // Set writeCache to contents of $data
331
+                // The writeCache will be carried over to the
332
+                // next write round, and added to the start of
333
+                // $data to ensure that written blocks are
334
+                // always the correct length. If there is still
335
+                // data in writeCache after the writing round
336
+                // has finished, then the data will be written
337
+                // to disk by $this->flush().
338
+                $this->writeCache = $data;
339
+
340
+                // Clear $data ready for next round
341
+                $data = '';
342
+
343
+            } else {
344
+
345
+                // Read the chunk from the start of $data
346
+                $chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
347
+
348
+                $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
349
+
350
+                // Remove the chunk we just processed from
351
+                // $data, leaving only unprocessed data in $data
352
+                // var, for handling on the next round
353
+                $data = substr($data, $this->unencryptedBlockSizeSigned);
354
+
355
+            }
356
+
357
+        }
358
+
359
+        return $encrypted;
360
+    }
361
+
362
+    /**
363
+     * decrypt data
364
+     *
365
+     * @param string $data you want to decrypt
366
+     * @param int $position
367
+     * @return string decrypted data
368
+     * @throws DecryptionFailedException
369
+     */
370
+    public function decrypt($data, $position = 0) {
371
+        if (empty($this->fileKey)) {
372
+            $msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
373
+            $hint = $this->l->t('Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
374
+            $this->logger->error($msg);
375
+
376
+            throw new DecryptionFailedException($msg, $hint);
377
+        }
378
+
379
+        return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
380
+    }
381
+
382
+    /**
383
+     * update encrypted file, e.g. give additional users access to the file
384
+     *
385
+     * @param string $path path to the file which should be updated
386
+     * @param string $uid of the user who performs the operation
387
+     * @param array $accessList who has access to the file contains the key 'users' and 'public'
388
+     * @return boolean
389
+     */
390
+    public function update($path, $uid, array $accessList) {
391
+
392
+        if (empty($accessList)) {
393
+            if (isset(self::$rememberVersion[$path])) {
394
+                $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
395
+                unset(self::$rememberVersion[$path]);
396
+            }
397
+            return;
398
+        }
399
+
400
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
401
+
402
+        if (!empty($fileKey)) {
403
+
404
+            $publicKeys = array();
405
+            if ($this->useMasterPassword === true) {
406
+                $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
407
+            } else {
408
+                foreach ($accessList['users'] as $user) {
409
+                    try {
410
+                        $publicKeys[$user] = $this->keyManager->getPublicKey($user);
411
+                    } catch (PublicKeyMissingException $e) {
412
+                        $this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
413
+                    }
414
+                }
415
+            }
416
+
417
+            $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
418
+
419
+            $encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
420
+
421
+            $this->keyManager->deleteAllFileKeys($path);
422
+
423
+            $this->keyManager->setAllFileKeys($path, $encryptedFileKey);
424
+
425
+        } else {
426
+            $this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
427
+                array('file' => $path, 'app' => 'encryption'));
428
+
429
+            return false;
430
+        }
431
+
432
+        return true;
433
+    }
434
+
435
+    /**
436
+     * should the file be encrypted or not
437
+     *
438
+     * @param string $path
439
+     * @return boolean
440
+     */
441
+    public function shouldEncrypt($path) {
442
+        if ($this->util->shouldEncryptHomeStorage() === false) {
443
+            $storage = $this->util->getStorage($path);
444
+            if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
445
+                return false;
446
+            }
447
+        }
448
+        $parts = explode('/', $path);
449
+        if (count($parts) < 4) {
450
+            return false;
451
+        }
452
+
453
+        if ($parts[2] === 'files') {
454
+            return true;
455
+        }
456
+        if ($parts[2] === 'files_versions') {
457
+            return true;
458
+        }
459
+        if ($parts[2] === 'files_trashbin') {
460
+            return true;
461
+        }
462
+
463
+        return false;
464
+    }
465
+
466
+    /**
467
+     * get size of the unencrypted payload per block.
468
+     * Nextcloud read/write files with a block size of 8192 byte
469
+     *
470
+     * @param bool $signed
471
+     * @return int
472
+     */
473
+    public function getUnencryptedBlockSize($signed = false) {
474
+        if ($signed === false) {
475
+            return $this->unencryptedBlockSize;
476
+        }
477
+
478
+        return $this->unencryptedBlockSizeSigned;
479
+    }
480
+
481
+    /**
482
+     * check if the encryption module is able to read the file,
483
+     * e.g. if all encryption keys exists
484
+     *
485
+     * @param string $path
486
+     * @param string $uid user for whom we want to check if he can read the file
487
+     * @return bool
488
+     * @throws DecryptionFailedException
489
+     */
490
+    public function isReadable($path, $uid) {
491
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
492
+        if (empty($fileKey)) {
493
+            $owner = $this->util->getOwner($path);
494
+            if ($owner !== $uid) {
495
+                // if it is a shared file we throw a exception with a useful
496
+                // error message because in this case it means that the file was
497
+                // shared with the user at a point where the user didn't had a
498
+                // valid private/public key
499
+                $msg = 'Encryption module "' . $this->getDisplayName() .
500
+                    '" is not able to read ' . $path;
501
+                $hint = $this->l->t('Can not read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
502
+                $this->logger->warning($msg);
503
+                throw new DecryptionFailedException($msg, $hint);
504
+            }
505
+            return false;
506
+        }
507
+
508
+        return true;
509
+    }
510
+
511
+    /**
512
+     * Initial encryption of all files
513
+     *
514
+     * @param InputInterface $input
515
+     * @param OutputInterface $output write some status information to the terminal during encryption
516
+     */
517
+    public function encryptAll(InputInterface $input, OutputInterface $output) {
518
+        $this->encryptAll->encryptAll($input, $output);
519
+    }
520
+
521
+    /**
522
+     * prepare module to perform decrypt all operation
523
+     *
524
+     * @param InputInterface $input
525
+     * @param OutputInterface $output
526
+     * @param string $user
527
+     * @return bool
528
+     */
529
+    public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
530
+        return $this->decryptAll->prepare($input, $output, $user);
531
+    }
532
+
533
+
534
+    /**
535
+     * @param string $path
536
+     * @return string
537
+     */
538
+    protected function getPathToRealFile($path) {
539
+        $realPath = $path;
540
+        $parts = explode('/', $path);
541
+        if ($parts[2] === 'files_versions') {
542
+            $realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
543
+            $length = strrpos($realPath, '.');
544
+            $realPath = substr($realPath, 0, $length);
545
+        }
546
+
547
+        return $realPath;
548
+    }
549
+
550
+    /**
551
+     * remove .part file extension and the ocTransferId from the file to get the
552
+     * original file name
553
+     *
554
+     * @param string $path
555
+     * @return string
556
+     */
557
+    protected function stripPartFileExtension($path) {
558
+        if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
559
+            $pos = strrpos($path, '.', -6);
560
+            $path = substr($path, 0, $pos);
561
+        }
562
+
563
+        return $path;
564
+    }
565
+
566
+    /**
567
+     * get owner of a file
568
+     *
569
+     * @param string $path
570
+     * @return string
571
+     */
572
+    protected function getOwner($path) {
573
+        if (!isset($this->owner[$path])) {
574
+            $this->owner[$path] = $this->util->getOwner($path);
575
+        }
576
+        return $this->owner[$path];
577
+    }
578
+
579
+    /**
580
+     * Check if the module is ready to be used by that specific user.
581
+     * In case a module is not ready - because e.g. key pairs have not been generated
582
+     * upon login this method can return false before any operation starts and might
583
+     * cause issues during operations.
584
+     *
585
+     * @param string $user
586
+     * @return boolean
587
+     * @since 9.1.0
588
+     */
589
+    public function isReadyForUser($user) {
590
+        return $this->keyManager->userHasKeys($user);
591
+    }
592
+
593
+    /**
594
+     * We only need a detailed access list if the master key is not enabled
595
+     *
596
+     * @return bool
597
+     */
598
+    public function needDetailedAccessList() {
599
+        return !$this->util->isMasterKeyEnabled();
600
+    }
601 601
 }
Please login to merge, or discard this patch.