Passed
Push — master ( baa5e9...248891 )
by Morris
11:48 queued 14s
created
apps/encryption/lib/Crypto/Encryption.php 1 patch
Indentation   +549 added lines, -549 removed lines patch added patch discarded remove patch
@@ -45,553 +45,553 @@
 block discarded – undo
45 45
 use Symfony\Component\Console\Output\OutputInterface;
46 46
 
47 47
 class Encryption implements IEncryptionModule {
48
-	public const ID = 'OC_DEFAULT_MODULE';
49
-	public 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 ['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 = [];
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
-		$encrypted = '';
318
-		// While there still remains some data to be processed & written
319
-		while (strlen($data) > 0) {
320
-
321
-			// Remaining length for this iteration, not of the
322
-			// entire file (may be greater than 8192 bytes)
323
-			$remainingLength = strlen($data);
324
-
325
-			// If data remaining to be written is less than the
326
-			// size of 1 6126 byte block
327
-			if ($remainingLength < $this->unencryptedBlockSizeSigned) {
328
-
329
-				// Set writeCache to contents of $data
330
-				// The writeCache will be carried over to the
331
-				// next write round, and added to the start of
332
-				// $data to ensure that written blocks are
333
-				// always the correct length. If there is still
334
-				// data in writeCache after the writing round
335
-				// has finished, then the data will be written
336
-				// to disk by $this->flush().
337
-				$this->writeCache = $data;
338
-
339
-				// Clear $data ready for next round
340
-				$data = '';
341
-			} else {
342
-
343
-				// Read the chunk from the start of $data
344
-				$chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
345
-
346
-				$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
347
-
348
-				// Remove the chunk we just processed from
349
-				// $data, leaving only unprocessed data in $data
350
-				// var, for handling on the next round
351
-				$data = substr($data, $this->unencryptedBlockSizeSigned);
352
-			}
353
-		}
354
-
355
-		return $encrypted;
356
-	}
357
-
358
-	/**
359
-	 * decrypt data
360
-	 *
361
-	 * @param string $data you want to decrypt
362
-	 * @param int $position
363
-	 * @return string decrypted data
364
-	 * @throws DecryptionFailedException
365
-	 */
366
-	public function decrypt($data, $position = 0) {
367
-		if (empty($this->fileKey)) {
368
-			$msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
369
-			$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.');
370
-			$this->logger->error($msg);
371
-
372
-			throw new DecryptionFailedException($msg, $hint);
373
-		}
374
-
375
-		return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
376
-	}
377
-
378
-	/**
379
-	 * update encrypted file, e.g. give additional users access to the file
380
-	 *
381
-	 * @param string $path path to the file which should be updated
382
-	 * @param string $uid of the user who performs the operation
383
-	 * @param array $accessList who has access to the file contains the key 'users' and 'public'
384
-	 * @return boolean
385
-	 */
386
-	public function update($path, $uid, array $accessList) {
387
-		if (empty($accessList)) {
388
-			if (isset(self::$rememberVersion[$path])) {
389
-				$this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
390
-				unset(self::$rememberVersion[$path]);
391
-			}
392
-			return;
393
-		}
394
-
395
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
396
-
397
-		if (!empty($fileKey)) {
398
-			$publicKeys = [];
399
-			if ($this->useMasterPassword === true) {
400
-				$publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
401
-			} else {
402
-				foreach ($accessList['users'] as $user) {
403
-					try {
404
-						$publicKeys[$user] = $this->keyManager->getPublicKey($user);
405
-					} catch (PublicKeyMissingException $e) {
406
-						$this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
407
-					}
408
-				}
409
-			}
410
-
411
-			$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
412
-
413
-			$encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
414
-
415
-			$this->keyManager->deleteAllFileKeys($path);
416
-
417
-			$this->keyManager->setAllFileKeys($path, $encryptedFileKey);
418
-		} else {
419
-			$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
420
-				['file' => $path, 'app' => 'encryption']);
421
-
422
-			return false;
423
-		}
424
-
425
-		return true;
426
-	}
427
-
428
-	/**
429
-	 * should the file be encrypted or not
430
-	 *
431
-	 * @param string $path
432
-	 * @return boolean
433
-	 */
434
-	public function shouldEncrypt($path) {
435
-		if ($this->util->shouldEncryptHomeStorage() === false) {
436
-			$storage = $this->util->getStorage($path);
437
-			if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
438
-				return false;
439
-			}
440
-		}
441
-		$parts = explode('/', $path);
442
-		if (count($parts) < 4) {
443
-			return false;
444
-		}
445
-
446
-		if ($parts[2] === 'files') {
447
-			return true;
448
-		}
449
-		if ($parts[2] === 'files_versions') {
450
-			return true;
451
-		}
452
-		if ($parts[2] === 'files_trashbin') {
453
-			return true;
454
-		}
455
-
456
-		return false;
457
-	}
458
-
459
-	/**
460
-	 * get size of the unencrypted payload per block.
461
-	 * Nextcloud read/write files with a block size of 8192 byte
462
-	 *
463
-	 * @param bool $signed
464
-	 * @return int
465
-	 */
466
-	public function getUnencryptedBlockSize($signed = false) {
467
-		if ($signed === false) {
468
-			return $this->unencryptedBlockSize;
469
-		}
470
-
471
-		return $this->unencryptedBlockSizeSigned;
472
-	}
473
-
474
-	/**
475
-	 * check if the encryption module is able to read the file,
476
-	 * e.g. if all encryption keys exists
477
-	 *
478
-	 * @param string $path
479
-	 * @param string $uid user for whom we want to check if he can read the file
480
-	 * @return bool
481
-	 * @throws DecryptionFailedException
482
-	 */
483
-	public function isReadable($path, $uid) {
484
-		$fileKey = $this->keyManager->getFileKey($path, $uid);
485
-		if (empty($fileKey)) {
486
-			$owner = $this->util->getOwner($path);
487
-			if ($owner !== $uid) {
488
-				// if it is a shared file we throw a exception with a useful
489
-				// error message because in this case it means that the file was
490
-				// shared with the user at a point where the user didn't had a
491
-				// valid private/public key
492
-				$msg = 'Encryption module "' . $this->getDisplayName() .
493
-					'" is not able to read ' . $path;
494
-				$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.');
495
-				$this->logger->warning($msg);
496
-				throw new DecryptionFailedException($msg, $hint);
497
-			}
498
-			return false;
499
-		}
500
-
501
-		return true;
502
-	}
503
-
504
-	/**
505
-	 * Initial encryption of all files
506
-	 *
507
-	 * @param InputInterface $input
508
-	 * @param OutputInterface $output write some status information to the terminal during encryption
509
-	 */
510
-	public function encryptAll(InputInterface $input, OutputInterface $output) {
511
-		$this->encryptAll->encryptAll($input, $output);
512
-	}
513
-
514
-	/**
515
-	 * prepare module to perform decrypt all operation
516
-	 *
517
-	 * @param InputInterface $input
518
-	 * @param OutputInterface $output
519
-	 * @param string $user
520
-	 * @return bool
521
-	 */
522
-	public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
523
-		return $this->decryptAll->prepare($input, $output, $user);
524
-	}
525
-
526
-
527
-	/**
528
-	 * @param string $path
529
-	 * @return string
530
-	 */
531
-	protected function getPathToRealFile($path) {
532
-		$realPath = $path;
533
-		$parts = explode('/', $path);
534
-		if ($parts[2] === 'files_versions') {
535
-			$realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
536
-			$length = strrpos($realPath, '.');
537
-			$realPath = substr($realPath, 0, $length);
538
-		}
539
-
540
-		return $realPath;
541
-	}
542
-
543
-	/**
544
-	 * remove .part file extension and the ocTransferId from the file to get the
545
-	 * original file name
546
-	 *
547
-	 * @param string $path
548
-	 * @return string
549
-	 */
550
-	protected function stripPartFileExtension($path) {
551
-		if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
552
-			$pos = strrpos($path, '.', -6);
553
-			$path = substr($path, 0, $pos);
554
-		}
555
-
556
-		return $path;
557
-	}
558
-
559
-	/**
560
-	 * get owner of a file
561
-	 *
562
-	 * @param string $path
563
-	 * @return string
564
-	 */
565
-	protected function getOwner($path) {
566
-		if (!isset($this->owner[$path])) {
567
-			$this->owner[$path] = $this->util->getOwner($path);
568
-		}
569
-		return $this->owner[$path];
570
-	}
571
-
572
-	/**
573
-	 * Check if the module is ready to be used by that specific user.
574
-	 * In case a module is not ready - because e.g. key pairs have not been generated
575
-	 * upon login this method can return false before any operation starts and might
576
-	 * cause issues during operations.
577
-	 *
578
-	 * @param string $user
579
-	 * @return boolean
580
-	 * @since 9.1.0
581
-	 */
582
-	public function isReadyForUser($user) {
583
-		if ($this->util->isMasterKeyEnabled()) {
584
-			return true;
585
-		}
586
-		return $this->keyManager->userHasKeys($user);
587
-	}
588
-
589
-	/**
590
-	 * We only need a detailed access list if the master key is not enabled
591
-	 *
592
-	 * @return bool
593
-	 */
594
-	public function needDetailedAccessList() {
595
-		return !$this->util->isMasterKeyEnabled();
596
-	}
48
+    public const ID = 'OC_DEFAULT_MODULE';
49
+    public 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 ['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 = [];
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
+        $encrypted = '';
318
+        // While there still remains some data to be processed & written
319
+        while (strlen($data) > 0) {
320
+
321
+            // Remaining length for this iteration, not of the
322
+            // entire file (may be greater than 8192 bytes)
323
+            $remainingLength = strlen($data);
324
+
325
+            // If data remaining to be written is less than the
326
+            // size of 1 6126 byte block
327
+            if ($remainingLength < $this->unencryptedBlockSizeSigned) {
328
+
329
+                // Set writeCache to contents of $data
330
+                // The writeCache will be carried over to the
331
+                // next write round, and added to the start of
332
+                // $data to ensure that written blocks are
333
+                // always the correct length. If there is still
334
+                // data in writeCache after the writing round
335
+                // has finished, then the data will be written
336
+                // to disk by $this->flush().
337
+                $this->writeCache = $data;
338
+
339
+                // Clear $data ready for next round
340
+                $data = '';
341
+            } else {
342
+
343
+                // Read the chunk from the start of $data
344
+                $chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
345
+
346
+                $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
347
+
348
+                // Remove the chunk we just processed from
349
+                // $data, leaving only unprocessed data in $data
350
+                // var, for handling on the next round
351
+                $data = substr($data, $this->unencryptedBlockSizeSigned);
352
+            }
353
+        }
354
+
355
+        return $encrypted;
356
+    }
357
+
358
+    /**
359
+     * decrypt data
360
+     *
361
+     * @param string $data you want to decrypt
362
+     * @param int $position
363
+     * @return string decrypted data
364
+     * @throws DecryptionFailedException
365
+     */
366
+    public function decrypt($data, $position = 0) {
367
+        if (empty($this->fileKey)) {
368
+            $msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
369
+            $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.');
370
+            $this->logger->error($msg);
371
+
372
+            throw new DecryptionFailedException($msg, $hint);
373
+        }
374
+
375
+        return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
376
+    }
377
+
378
+    /**
379
+     * update encrypted file, e.g. give additional users access to the file
380
+     *
381
+     * @param string $path path to the file which should be updated
382
+     * @param string $uid of the user who performs the operation
383
+     * @param array $accessList who has access to the file contains the key 'users' and 'public'
384
+     * @return boolean
385
+     */
386
+    public function update($path, $uid, array $accessList) {
387
+        if (empty($accessList)) {
388
+            if (isset(self::$rememberVersion[$path])) {
389
+                $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
390
+                unset(self::$rememberVersion[$path]);
391
+            }
392
+            return;
393
+        }
394
+
395
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
396
+
397
+        if (!empty($fileKey)) {
398
+            $publicKeys = [];
399
+            if ($this->useMasterPassword === true) {
400
+                $publicKeys[$this->keyManager->getMasterKeyId()] = $this->keyManager->getPublicMasterKey();
401
+            } else {
402
+                foreach ($accessList['users'] as $user) {
403
+                    try {
404
+                        $publicKeys[$user] = $this->keyManager->getPublicKey($user);
405
+                    } catch (PublicKeyMissingException $e) {
406
+                        $this->logger->warning('Could not encrypt file for ' . $user . ': ' . $e->getMessage());
407
+                    }
408
+                }
409
+            }
410
+
411
+            $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
412
+
413
+            $encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
414
+
415
+            $this->keyManager->deleteAllFileKeys($path);
416
+
417
+            $this->keyManager->setAllFileKeys($path, $encryptedFileKey);
418
+        } else {
419
+            $this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
420
+                ['file' => $path, 'app' => 'encryption']);
421
+
422
+            return false;
423
+        }
424
+
425
+        return true;
426
+    }
427
+
428
+    /**
429
+     * should the file be encrypted or not
430
+     *
431
+     * @param string $path
432
+     * @return boolean
433
+     */
434
+    public function shouldEncrypt($path) {
435
+        if ($this->util->shouldEncryptHomeStorage() === false) {
436
+            $storage = $this->util->getStorage($path);
437
+            if ($storage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
438
+                return false;
439
+            }
440
+        }
441
+        $parts = explode('/', $path);
442
+        if (count($parts) < 4) {
443
+            return false;
444
+        }
445
+
446
+        if ($parts[2] === 'files') {
447
+            return true;
448
+        }
449
+        if ($parts[2] === 'files_versions') {
450
+            return true;
451
+        }
452
+        if ($parts[2] === 'files_trashbin') {
453
+            return true;
454
+        }
455
+
456
+        return false;
457
+    }
458
+
459
+    /**
460
+     * get size of the unencrypted payload per block.
461
+     * Nextcloud read/write files with a block size of 8192 byte
462
+     *
463
+     * @param bool $signed
464
+     * @return int
465
+     */
466
+    public function getUnencryptedBlockSize($signed = false) {
467
+        if ($signed === false) {
468
+            return $this->unencryptedBlockSize;
469
+        }
470
+
471
+        return $this->unencryptedBlockSizeSigned;
472
+    }
473
+
474
+    /**
475
+     * check if the encryption module is able to read the file,
476
+     * e.g. if all encryption keys exists
477
+     *
478
+     * @param string $path
479
+     * @param string $uid user for whom we want to check if he can read the file
480
+     * @return bool
481
+     * @throws DecryptionFailedException
482
+     */
483
+    public function isReadable($path, $uid) {
484
+        $fileKey = $this->keyManager->getFileKey($path, $uid);
485
+        if (empty($fileKey)) {
486
+            $owner = $this->util->getOwner($path);
487
+            if ($owner !== $uid) {
488
+                // if it is a shared file we throw a exception with a useful
489
+                // error message because in this case it means that the file was
490
+                // shared with the user at a point where the user didn't had a
491
+                // valid private/public key
492
+                $msg = 'Encryption module "' . $this->getDisplayName() .
493
+                    '" is not able to read ' . $path;
494
+                $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.');
495
+                $this->logger->warning($msg);
496
+                throw new DecryptionFailedException($msg, $hint);
497
+            }
498
+            return false;
499
+        }
500
+
501
+        return true;
502
+    }
503
+
504
+    /**
505
+     * Initial encryption of all files
506
+     *
507
+     * @param InputInterface $input
508
+     * @param OutputInterface $output write some status information to the terminal during encryption
509
+     */
510
+    public function encryptAll(InputInterface $input, OutputInterface $output) {
511
+        $this->encryptAll->encryptAll($input, $output);
512
+    }
513
+
514
+    /**
515
+     * prepare module to perform decrypt all operation
516
+     *
517
+     * @param InputInterface $input
518
+     * @param OutputInterface $output
519
+     * @param string $user
520
+     * @return bool
521
+     */
522
+    public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') {
523
+        return $this->decryptAll->prepare($input, $output, $user);
524
+    }
525
+
526
+
527
+    /**
528
+     * @param string $path
529
+     * @return string
530
+     */
531
+    protected function getPathToRealFile($path) {
532
+        $realPath = $path;
533
+        $parts = explode('/', $path);
534
+        if ($parts[2] === 'files_versions') {
535
+            $realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3));
536
+            $length = strrpos($realPath, '.');
537
+            $realPath = substr($realPath, 0, $length);
538
+        }
539
+
540
+        return $realPath;
541
+    }
542
+
543
+    /**
544
+     * remove .part file extension and the ocTransferId from the file to get the
545
+     * original file name
546
+     *
547
+     * @param string $path
548
+     * @return string
549
+     */
550
+    protected function stripPartFileExtension($path) {
551
+        if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
552
+            $pos = strrpos($path, '.', -6);
553
+            $path = substr($path, 0, $pos);
554
+        }
555
+
556
+        return $path;
557
+    }
558
+
559
+    /**
560
+     * get owner of a file
561
+     *
562
+     * @param string $path
563
+     * @return string
564
+     */
565
+    protected function getOwner($path) {
566
+        if (!isset($this->owner[$path])) {
567
+            $this->owner[$path] = $this->util->getOwner($path);
568
+        }
569
+        return $this->owner[$path];
570
+    }
571
+
572
+    /**
573
+     * Check if the module is ready to be used by that specific user.
574
+     * In case a module is not ready - because e.g. key pairs have not been generated
575
+     * upon login this method can return false before any operation starts and might
576
+     * cause issues during operations.
577
+     *
578
+     * @param string $user
579
+     * @return boolean
580
+     * @since 9.1.0
581
+     */
582
+    public function isReadyForUser($user) {
583
+        if ($this->util->isMasterKeyEnabled()) {
584
+            return true;
585
+        }
586
+        return $this->keyManager->userHasKeys($user);
587
+    }
588
+
589
+    /**
590
+     * We only need a detailed access list if the master key is not enabled
591
+     *
592
+     * @return bool
593
+     */
594
+    public function needDetailedAccessList() {
595
+        return !$this->util->isMasterKeyEnabled();
596
+    }
597 597
 }
Please login to merge, or discard this patch.