Completed
Push — stable8 ( c45eda...dba072 )
by
unknown
15:55
created

Util   F

Complexity

Total Complexity 200

Size/Duplication

Total Lines 1664
Duplicated Lines 7.09 %

Coupling/Cohesion

Components 1
Dependencies 23

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 200
lcom 1
cbo 23
dl 118
loc 1664
rs 0.6197
c 1
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
B isSystemWideMountPoint() 0 14 5
A isMountPointApplicableToUser() 0 15 4
B __construct() 0 33 2
B ready() 0 13 5
A userKeysExists() 0 9 3
A replaceUserKeys() 0 6 1
C setupServerSide() 0 67 11
A getPublicShareKeyId() 0 3 1
A recoveryEnabledForUser() 0 7 2
A setRecoveryForUser() 0 11 3
D findEncFiles() 0 85 14
B isEncryptedPath() 0 36 6
C getFileSize() 0 90 12
A containHeader() 0 17 2
B fixFileSize() 0 29 2
B encryptVersions() 40 40 6
B decryptVersions() 40 40 6
D decryptAll() 15 108 10
B encryptAll() 0 80 6
B getPath() 0 38 6
A isUserReady() 0 15 4
B filterShareReadyUsers() 0 34 3
A decryptKeyfile() 0 12 1
B setSharedFileKeyfiles() 0 60 6
C getSharingUsersArray() 0 58 10
A getUserWithAccessToMountPoint() 0 13 3
A setMigrationStatus() 0 13 3
A beginMigration() 12 12 2
A resetMigrationStatus() 0 4 1
A finishMigration() 11 11 2
A getMigrationStatus() 0 14 3
C getUidAndFilename() 0 64 9
B getAllFiles() 0 25 5
B getOwnerFromSharedFile() 0 54 8
A getUserId() 0 3 1
A getKeyId() 0 3 1
A getUserFilesDir() 0 3 1
A checkRecoveryPassword() 0 13 2
A getRecoveryKeyId() 0 3 1
A addRecoveryKeys() 0 15 3
A removeRecoveryKeys() 0 12 3
B recoverFile() 0 32 3
A recoverAllFiles() 0 12 3
A recoverUsersFiles() 0 7 1
A backupAllKeys() 0 11 3
B restoreBackup() 0 23 6
A deleteBackup() 0 4 1
B initEncryption() 0 25 3
A closeEncryptionSession() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Util often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Util, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ownCloud
4
 *
5
 * @copyright (C) 2014 ownCloud, Inc.
6
 *
7
 * @author Sam Tuke <[email protected]>,
8
 * @author Frank Karlitschek <[email protected]>,
9
 * @author Bjoern Schiessle <[email protected]>
10
 *
11
 * This library is free software; you can redistribute it and/or
12
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
13
 * License as published by the Free Software Foundation; either
14
 * version 3 of the License, or any later version.
15
 *
16
 * This library is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public
22
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 */
25
26
namespace OCA\Files_Encryption;
27
use OC\User\NoUserException;
28
29
/**
30
 * Class for utilities relating to encrypted file storage system
31
 * @param \OC\Files\View $view expected to have OC '/' as root path
32
 * @param string $userId ID of the logged in user
33
 * @param int $client indicating status of client side encryption. Currently
34
 * unused, likely to become obsolete shortly
35
 */
36
37
class Util {
38
39
	const MIGRATION_COMPLETED = 1;    // migration to new encryption completed
40
	const MIGRATION_IN_PROGRESS = -1; // migration is running
41
	const MIGRATION_OPEN = 0;         // user still needs to be migrated
42
43
	const FILE_TYPE_FILE = 0;
44
	const FILE_TYPE_VERSION = 1;
45
	const FILE_TYPE_CACHE = 2;
46
47
	/**
48
	 * @var \OC\Files\View
49
	 */
50
	private $view; // OC\Files\View object for filesystem operations
51
52
	/**
53
	 * @var string
54
	 */
55
	private $userId; // ID of the user we use to encrypt/decrypt files
56
57
	/**
58
	 * @var string
59
	 */
60
	private $keyId; // ID of the key we want to manipulate
61
62
	/**
63
	 * @var bool
64
	 */
65
	private $client; // Client side encryption mode flag
66
67
	/**
68
	 * @var string
69
	 */
70
	private $publicKeyDir; // Dir containing all public user keys
71
72
	/**
73
	 * @var string
74
	 */
75
	private $encryptionDir; // Dir containing user's files_encryption
76
77
	/**
78
	 * @var string
79
	 */
80
	private $keysPath; // Dir containing all file related encryption keys
81
82
	/**
83
	 * @var string
84
	 */
85
	private $publicKeyPath; // Path to user's public key
86
87
	/**
88
	 * @var string
89
	 */
90
	private $privateKeyPath; // Path to user's private key
91
92
	/**
93
	 * @var string
94
	 */
95
	private $userFilesDir;
96
97
	/**
98
	 * @var string
99
	 */
100
	private $publicShareKeyId;
101
102
	/**
103
	 * @var string
104
	 */
105
	private $recoveryKeyId;
106
107
	/**
108
	 * @var bool
109
	 */
110
	private $isPublic;
111
112
	/**
113
	 * @param \OC\Files\View $view
114
	 * @param string $userId
115
	 * @param bool $client
116
	 */
117
	public function __construct($view, $userId, $client = false) {
118
119
		$this->view = $view;
120
		$this->client = $client;
121
		$this->userId = $userId;
122
123
		$appConfig = \OC::$server->getAppConfig();
124
125
		$this->publicShareKeyId = $appConfig->getValue('files_encryption', 'publicShareKeyId');
126
		$this->recoveryKeyId = $appConfig->getValue('files_encryption', 'recoveryKeyId');
127
128
		$this->userDir = '/' . $this->userId;
0 ignored issues
show
Bug introduced by
The property userDir does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
129
		$this->fileFolderName = 'files';
0 ignored issues
show
Bug introduced by
The property fileFolderName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
130
		$this->userFilesDir =
131
				'/' . $userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable?
132
		$this->publicKeyDir = Keymanager::getPublicKeyPath();
133
		$this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption';
134
		$this->keysPath = $this->encryptionDir . '/' . 'keys';
135
		$this->publicKeyPath =
136
				$this->publicKeyDir . '/' . $this->userId . '.publicKey'; // e.g. data/public-keys/admin.publicKey
137
		$this->privateKeyPath =
138
				$this->encryptionDir . '/' . $this->userId . '.privateKey'; // e.g. data/admin/admin.privateKey
139
		// make sure that the owners home is mounted
140
		\OC\Files\Filesystem::initMountPoints($userId);
141
142
		if (Helper::isPublicAccess()) {
143
			$this->keyId = $this->publicShareKeyId;
144
			$this->isPublic = true;
145
		} else {
146
			$this->keyId = $this->userId;
147
			$this->isPublic = false;
148
		}
149
	}
150
151
	/**
152
	 * @return bool
153
	 */
154
	public function ready() {
155
156
		if (
157
			!$this->view->file_exists($this->encryptionDir)
158
			or !$this->view->file_exists($this->keysPath)
159
			or !$this->view->file_exists($this->publicKeyPath)
160
			or !$this->view->file_exists($this->privateKeyPath)
161
		) {
162
			return false;
163
		} else {
164
			return true;
165
		}
166
	}
167
168
	/**
169
	 * check if the users private & public key exists
170
	 * @return boolean
171
	 */
172
	public function userKeysExists() {
173
		if (
174
				$this->view->file_exists($this->privateKeyPath) &&
175
				$this->view->file_exists($this->publicKeyPath)) {
176
			return true;
177
		} else {
178
			return false;
179
		}
180
	}
181
182
	/**
183
	 * create a new public/private key pair for the user
184
	 *
185
	 * @param string $password password for the private key
186
	 */
187
	public function replaceUserKeys($password) {
188
		$this->backupAllKeys('password_reset');
189
		$this->view->unlink($this->publicKeyPath);
190
		$this->view->unlink($this->privateKeyPath);
191
		$this->setupServerSide($password);
192
	}
193
194
	/**
195
	 * Sets up user folders and keys for serverside encryption
196
	 *
197
	 * @param string $passphrase to encrypt server-stored private key with
198
	 * @return bool
199
	 */
200
	public function setupServerSide($passphrase = null) {
201
202
		// Set directories to check / create
203
		$setUpDirs = array(
204
			$this->userDir,
205
			$this->publicKeyDir,
206
			$this->encryptionDir,
207
			$this->keysPath
208
		);
209
210
		// Check / create all necessary dirs
211
		foreach ($setUpDirs as $dirPath) {
212
213
			if (!$this->view->file_exists($dirPath)) {
214
215
				$this->view->mkdir($dirPath);
216
217
			}
218
219
		}
220
221
		// Create user keypair
222
		// we should never override a keyfile
223
		if (
224
			!$this->view->file_exists($this->publicKeyPath)
225
			&& !$this->view->file_exists($this->privateKeyPath)
226
		) {
227
228
			// Generate keypair
229
			$keypair = Crypt::createKeypair();
230
231
			if ($keypair) {
232
233
				\OC_FileProxy::$enabled = false;
234
235
				// Encrypt private key with user pwd as passphrase
236
				$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase, Helper::getCipher());
237
238
				// Save key-pair
239
				if ($encryptedPrivateKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $encryptedPrivateKey of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
240
					$header = crypt::generateHeader();
241
					$this->view->file_put_contents($this->privateKeyPath, $header . $encryptedPrivateKey);
242
					$this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
243
				}
244
245
				\OC_FileProxy::$enabled = true;
246
			}
247
248
		} else {
249
			// check if public-key exists but private-key is missing
250
			if ($this->view->file_exists($this->publicKeyPath) && !$this->view->file_exists($this->privateKeyPath)) {
251
				\OCP\Util::writeLog('Encryption library',
252
					'public key exists but private key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL);
253
				return false;
254
			} else {
255
				if (!$this->view->file_exists($this->publicKeyPath) && $this->view->file_exists($this->privateKeyPath)
256
				) {
257
					\OCP\Util::writeLog('Encryption library',
258
						'private key exists but public key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL);
259
					return false;
260
				}
261
			}
262
		}
263
264
		return true;
265
266
	}
267
268
	/**
269
	 * @return string
270
	 */
271
	public function getPublicShareKeyId() {
272
		return $this->publicShareKeyId;
273
	}
274
275
	/**
276
	 * Check whether pwd recovery is enabled for a given user
277
	 * @return bool 1 = yes, 0 = no, false = no record
278
	 *
279
	 * @note If records are not being returned, check for a hidden space
280
	 *       at the start of the uid in db
281
	 */
282
	public function recoveryEnabledForUser() {
283
284
		$recoveryMode = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'recovery_enabled', '0');
285
286
		return ($recoveryMode === '1') ? true : false;
287
288
	}
289
290
	/**
291
	 * Enable / disable pwd recovery for a given user
292
	 * @param bool $enabled Whether to enable or disable recovery
293
	 * @return bool
294
	 */
295
	public function setRecoveryForUser($enabled) {
296
297
		$value = $enabled ? '1' : '0';
298
		try {
299
			\OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'recovery_enabled', $value);
300
			return true;
301
		} catch(\OCP\PreConditionNotMetException $e) {
302
			return false;
303
		}
304
305
	}
306
307
	/**
308
	 * Find all files and their encryption status within a directory
309
	 * @param string $directory The path of the parent directory to search
310
	 * @param bool $found the founded files if called again
311
	 * @return array keys: plain, encrypted, broken
312
	 * @note $directory needs to be a path relative to OC data dir. e.g.
313
	 *       /admin/files NOT /backup OR /home/www/oc/data/admin/files
314
	 */
315
	public function findEncFiles($directory, &$found = false) {
316
317
		// Disable proxy - we don't want files to be decrypted before
318
		// we handle them
319
		\OC_FileProxy::$enabled = false;
320
321
		if ($found === false) {
322
			$found = array(
323
				'plain' => array(),
324
				'encrypted' => array(),
325
				'broken' => array(),
326
			);
327
		}
328
329
		if ($this->view->is_dir($directory) && $handle = $this->view->opendir($directory)){
330
			if (is_resource($handle)) {
331
				while (false !== ($file = readdir($handle))) {
332
333
					if ($file !== "." && $file !== "..") {
334
						// skip stray part files
335
						if (Helper::isPartialFilePath($file)) {
336
							continue;
337
						}
338
339
						$filePath = $directory . '/' . $this->view->getRelativePath('/' . $file);
340
						$relPath = Helper::stripUserFilesPath($filePath);
341
342
						// If the path is a directory, search
343
						// its contents
344
						if ($this->view->is_dir($filePath)) {
345
346
							$this->findEncFiles($filePath, $found);
0 ignored issues
show
Bug introduced by
It seems like $found defined by array('plain' => array()...), 'broken' => array()) on line 322 can also be of type array<string,array,{"pla...ray","broken":"array"}>; however, OCA\Files_Encryption\Util::findEncFiles() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
347
348
							// If the path is a file, determine
349
							// its encryption status
350
						} elseif ($this->view->is_file($filePath)) {
351
352
							// Disable proxies again, some-
353
							// where they got re-enabled :/
354
							\OC_FileProxy::$enabled = false;
355
356
							$isEncryptedPath = $this->isEncryptedPath($filePath);
357
							// If the file is encrypted
358
							// NOTE: If the userId is
359
							// empty or not set, file will
360
							// detected as plain
361
							// NOTE: This is inefficient;
362
							// scanning every file like this
363
							// will eat server resources :(
364
							if ($isEncryptedPath) {
365
366
								$fileKey = Keymanager::getFileKey($this->view, $this, $relPath);
367
								$shareKey = Keymanager::getShareKey($this->view, $this->userId, $this, $relPath);
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...serFilesPath($filePath) on line 340 can also be of type false; however, OCA\Files_Encryption\Keymanager::getShareKey() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
368
								// if file is encrypted but now file key is available, throw exception
369
								if ($fileKey === false || $shareKey === false) {
370
									\OCP\Util::writeLog('encryption library', 'No keys available to decrypt the file: ' . $filePath, \OCP\Util::ERROR);
371
									$found['broken'][] = array(
372
										'name' => $file,
373
										'path' => $filePath,
374
									);
375
								} else {
376
									$found['encrypted'][] = array(
377
										'name' => $file,
378
										'path' => $filePath,
379
									);
380
								}
381
382
								// If the file is not encrypted
383
							} else {
384
385
								$found['plain'][] = array(
386
									'name' => $file,
387
									'path' => $relPath
388
								);
389
							}
390
						}
391
					}
392
				}
393
			}
394
		}
395
396
		\OC_FileProxy::$enabled = true;
397
398
		return $found;
399
	}
400
401
	/**
402
	 * Check if a given path identifies an encrypted file
403
	 * @param string $path
404
	 * @return boolean
405
	 */
406
	public function isEncryptedPath($path) {
407
408
		// Disable encryption proxy so data retrieved is in its
409
		// original form
410
		$proxyStatus = \OC_FileProxy::$enabled;
411
		\OC_FileProxy::$enabled = false;
412
413
		$data = '';
414
415
		// we only need 24 byte from the last chunk
416
		if ($this->view->file_exists($path)) {
417
			$handle = $this->view->fopen($path, 'r');
418
			if (is_resource($handle)) {
419
				// suppress fseek warining, we handle the case that fseek doesn't
420
				// work in the else branch
421
				if (@fseek($handle, -24, SEEK_END) === 0) {
422
					$data = fgets($handle);
423
				} else {
424
					// if fseek failed on the storage we create a local copy from the file
425
					// and read this one
426
					fclose($handle);
427
					$localFile = $this->view->getLocalFile($path);
428
					$handle = fopen($localFile, 'r');
429
					if (is_resource($handle) && fseek($handle, -24, SEEK_END) === 0) {
430
						$data = fgets($handle);
431
					}
432
				}
433
				fclose($handle);
434
			}
435
		}
436
437
		// re-enable proxy
438
		\OC_FileProxy::$enabled = $proxyStatus;
439
440
		return Crypt::isCatfileContent($data);
441
	}
442
443
	/**
444
	 * get the file size of the unencrypted file
445
	 * @param string $path absolute path
446
	 * @return bool
447
	 */
448
	public function getFileSize($path) {
449
450
		$result = 0;
451
452
		// Disable encryption proxy to prevent recursive calls
453
		$proxyStatus = \OC_FileProxy::$enabled;
454
		\OC_FileProxy::$enabled = false;
455
456
		// split the path parts
457
		$pathParts = explode('/', $path);
458
459
		if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path)
460
			&& $this->isEncryptedPath($path)
461
		) {
462
463
			$cipher = 'AES-128-CFB';
464
			$realSize = 0;
465
466
			// get the size from filesystem
467
			$size = $this->view->filesize($path);
468
469
			// open stream
470
			$stream = $this->view->fopen($path, "r");
471
472
			if (is_resource($stream)) {
473
474
				// if the file contains a encryption header we
475
				// we set the cipher
476
				// and we update the size
477
				if ($this->containHeader($path)) {
478
					$data = fread($stream,Crypt::BLOCKSIZE);
479
					$header = Crypt::parseHeader($data);
480
					$cipher = Crypt::getCipher($header);
481
					$size -= Crypt::BLOCKSIZE;
482
				}
483
484
				// fast path, else the calculation for $lastChunkNr is bogus
485
				if ($size === 0) {
486
					\OC_FileProxy::$enabled = $proxyStatus;
487
					return 0;
488
				}
489
490
				// calculate last chunk nr
491
				// next highest is end of chunks, one subtracted is last one
492
				// we have to read the last chunk, we can't just calculate it (because of padding etc)
493
				$lastChunkNr = ceil($size/Crypt::BLOCKSIZE)-1;
494
495
				// calculate last chunk position
496
				$lastChunkPos = ($lastChunkNr * Crypt::BLOCKSIZE);
497
498
				// get the content of the last chunk
499
				if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) {
500
					$realSize+=$lastChunkNr*6126;
501
				}
502
				$lastChunkContentEncrypted='';
503
				$count=Crypt::BLOCKSIZE;
504
				while ($count>0) {
505
					$data=fread($stream,Crypt::BLOCKSIZE);
506
					$count=strlen($data);
507
					$lastChunkContentEncrypted.=$data;
508
					if(strlen($lastChunkContentEncrypted)>Crypt::BLOCKSIZE) {
509
						$realSize+=6126;
510
						$lastChunkContentEncrypted=substr($lastChunkContentEncrypted,Crypt::BLOCKSIZE);
511
					}
512
				}
513
				fclose($stream);
514
				$relPath = Helper::stripUserFilesPath($path);
515
				$shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $relPath);
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...ripUserFilesPath($path) on line 514 can also be of type false; however, OCA\Files_Encryption\Keymanager::getShareKey() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
516
				if($shareKey===false) {
517
					\OC_FileProxy::$enabled = $proxyStatus;
518
					return $result;
519
				}
520
				$session = new Session($this->view);
521
				$privateKey = $session->getPrivateKey();
522
				$plainKeyfile = $this->decryptKeyfile($relPath, $privateKey);
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...ripUserFilesPath($path) on line 514 can also be of type false; however, OCA\Files_Encryption\Util::decryptKeyfile() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
523
				$plainKey = Crypt::multiKeyDecrypt($plainKeyfile, $shareKey, $privateKey);
0 ignored issues
show
Bug introduced by
It seems like $shareKey defined by \OCA\Files_Encryption\Ke...keyId, $this, $relPath) on line 515 can also be of type boolean; however, OCA\Files_Encryption\Crypt::multiKeyDecrypt() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
524
				$lastChunkContent=Crypt::symmetricDecryptFileContent($lastChunkContentEncrypted, $plainKey, $cipher);
525
526
				// calc the real file size with the size of the last chunk
527
				$realSize += strlen($lastChunkContent);
528
529
				// store file size
530
				$result = $realSize;
531
			}
532
		}
533
534
		\OC_FileProxy::$enabled = $proxyStatus;
535
536
		return $result;
537
	}
538
539
	/**
540
	 * check if encrypted file contain a encryption header
541
	 *
542
	 * @param string $path
543
	 * @return boolean
544
	 */
545
	private function containHeader($path) {
546
		// Disable encryption proxy to read the raw data
547
		$proxyStatus = \OC_FileProxy::$enabled;
548
		\OC_FileProxy::$enabled = false;
549
550
		$isHeader = false;
551
		$handle = $this->view->fopen($path, 'r');
552
553
		if (is_resource($handle)) {
554
			$firstBlock = fread($handle, Crypt::BLOCKSIZE);
555
			$isHeader =  Crypt::isHeader($firstBlock);
556
		}
557
558
		\OC_FileProxy::$enabled = $proxyStatus;
559
560
		return $isHeader;
561
	}
562
563
	/**
564
	 * fix the file size of the encrypted file
565
	 * @param string $path absolute path
566
	 * @return boolean true / false if file is encrypted
567
	 */
568
	public function fixFileSize($path) {
569
570
		$result = false;
571
572
		// Disable encryption proxy to prevent recursive calls
573
		$proxyStatus = \OC_FileProxy::$enabled;
574
		\OC_FileProxy::$enabled = false;
575
576
		$realSize = $this->getFileSize($path);
577
578
		if ($realSize > 0) {
579
580
			$cached = $this->view->getFileInfo($path);
581
			$cached['encrypted'] = true;
582
583
			// set the size
584
			$cached['unencrypted_size'] = $realSize;
585
586
			// put file info
587
			$this->view->putFileInfo($path, $cached);
588
589
			$result = true;
590
591
		}
592
593
		\OC_FileProxy::$enabled = $proxyStatus;
594
595
		return $result;
596
	}
597
598
	/**
599
	 * encrypt versions from given file
600
	 * @param array $filelist list of encrypted files, relative to data/user/files
601
	 * @return boolean
602
	 */
603 View Code Duplication
	private function encryptVersions($filelist) {
604
605
		$successful = true;
606
607
		if (\OCP\App::isEnabled('files_versions')) {
608
609
			foreach ($filelist as $filename) {
610
611
				$versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename);
612
				foreach ($versions as $version) {
613
614
					$path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version'];
615
616
					$encHandle = fopen('crypt://' . $path . '.part', 'wb');
617
618
					if ($encHandle === false) {
619
						\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL);
620
						$successful = false;
621
						continue;
622
					}
623
624
					$plainHandle = $this->view->fopen($path, 'rb');
625
					if ($plainHandle === false) {
626
						\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL);
627
						$successful = false;
628
						continue;
629
					}
630
631
					stream_copy_to_stream($plainHandle, $encHandle);
632
633
					fclose($encHandle);
634
					fclose($plainHandle);
635
636
					$this->view->rename($path . '.part', $path);
637
				}
638
			}
639
		}
640
641
		return $successful;
642
	}
643
644
	/**
645
	 * decrypt versions from given file
646
	 * @param string $filelist list of decrypted files, relative to data/user/files
647
	 * @return boolean
648
	 */
649 View Code Duplication
	private function decryptVersions($filelist) {
650
651
		$successful = true;
652
653
		if (\OCP\App::isEnabled('files_versions')) {
654
655
			foreach ($filelist as $filename) {
0 ignored issues
show
Bug introduced by
The expression $filelist of type string is not traversable.
Loading history...
656
657
				$versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename);
658
				foreach ($versions as $version) {
659
660
					$path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version'];
661
662
					$encHandle = fopen('crypt://' . $path, 'rb');
663
664
					if ($encHandle === false) {
665
						\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL);
666
						$successful = false;
667
						continue;
668
					}
669
670
					$plainHandle = $this->view->fopen($path . '.part', 'wb');
671
					if ($plainHandle === false) {
672
						\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL);
673
						$successful = false;
674
						continue;
675
					}
676
677
					stream_copy_to_stream($encHandle, $plainHandle);
678
679
					fclose($encHandle);
680
					fclose($plainHandle);
681
682
					$this->view->rename($path . '.part', $path);
683
				}
684
			}
685
		}
686
687
		return $successful;
688
	}
689
690
	/**
691
	 * Decrypt all files
692
	 * @return bool
693
	 */
694
	public function decryptAll() {
695
696
		$found = $this->findEncFiles($this->userId . '/files');
697
698
		$successful = true;
699
700
		if ($found) {
701
702
			$versionStatus = \OCP\App::isEnabled('files_versions');
703
			\OC_App::disable('files_versions');
704
705
			$decryptedFiles = array();
706
707
			// Encrypt unencrypted files
708
			foreach ($found['encrypted'] as $encryptedFile) {
709
710
				//relative to data/<user>/file
711
				$relPath = Helper::stripUserFilesPath($encryptedFile['path']);
712
713
				//get file info
714
				$fileInfo = \OC\Files\Filesystem::getFileInfo($relPath);
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...$encryptedFile['path']) on line 711 can also be of type false; however, OC\Files\Filesystem::getFileInfo() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
715
716
				//relative to /data
717
				$rawPath = $encryptedFile['path'];
718
719
				//get timestamp
720
				$timestamp = $fileInfo['mtime'];
721
722
				//enable proxy to use OC\Files\View to access the original file
723
				\OC_FileProxy::$enabled = true;
724
725
				// Open enc file handle for binary reading
726
				$encHandle = $this->view->fopen($rawPath, 'rb');
727
728
				// Disable proxy to prevent file being encrypted again
729
				\OC_FileProxy::$enabled = false;
730
731 View Code Duplication
				if ($encHandle === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
732
					\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL);
733
					$successful = false;
734
					continue;
735
				}
736
737
				// Open plain file handle for binary writing, with same filename as original plain file
738
				$plainHandle = $this->view->fopen($rawPath . '.part', 'wb');
739 View Code Duplication
				if ($plainHandle === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
740
					\OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '.part", decryption failed!', \OCP\Util::FATAL);
741
					$successful = false;
742
					continue;
743
				}
744
745
				// Move plain file to a temporary location
746
				$size = stream_copy_to_stream($encHandle, $plainHandle);
747 View Code Duplication
				if ($size === 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
748
					\OCP\Util::writeLog('Encryption library', 'Zero bytes copied of "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL);
749
					$successful = false;
750
					continue;
751
				}
752
753
				fclose($encHandle);
754
				fclose($plainHandle);
755
756
				$fakeRoot = $this->view->getRoot();
757
				$this->view->chroot('/' . $this->userId . '/files');
758
759
				$this->view->rename($relPath . '.part', $relPath);
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...$encryptedFile['path']) on line 711 can also be of type false; however, OC\Files\View::rename() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
760
761
				//set timestamp
762
				$this->view->touch($relPath, $timestamp);
763
764
				$this->view->chroot($fakeRoot);
765
766
				// Add the file to the cache
767
				\OC\Files\Filesystem::putFileInfo($relPath, array(
0 ignored issues
show
Security Bug introduced by
It seems like $relPath defined by \OCA\Files_Encryption\He...$encryptedFile['path']) on line 711 can also be of type false; however, OC\Files\Filesystem::putFileInfo() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
768
					'encrypted' => false,
769
					'size' => $size,
770
					'unencrypted_size' => 0,
771
					'etag' => $fileInfo['etag']
772
				));
773
774
				$decryptedFiles[] = $relPath;
775
776
			}
777
778
			if ($versionStatus) {
779
				\OC_App::enable('files_versions');
780
			}
781
782
			if (!$this->decryptVersions($decryptedFiles)) {
0 ignored issues
show
Documentation introduced by
$decryptedFiles is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
783
				$successful = false;
784
			}
785
786
			// if there are broken encrypted files than the complete decryption
787
			// was not successful
788
			if (!empty($found['broken'])) {
789
				$successful = false;
790
			}
791
792
			if ($successful) {
793
				$this->backupAllKeys('decryptAll', false, false);
794
				$this->view->deleteAll($this->keysPath);
795
			}
796
797
			\OC_FileProxy::$enabled = true;
798
		}
799
800
		return $successful;
801
	}
802
803
	/**
804
	 * Encrypt all files in a directory
805
	 * @param string $dirPath the directory whose files will be encrypted
806
	 * @return bool
807
	 * @note Encryption is recursive
808
	 */
809
	public function encryptAll($dirPath) {
810
811
		$result = true;
812
813
		$found = $this->findEncFiles($dirPath);
814
815
		// Disable proxy to prevent file being encrypted twice
816
		\OC_FileProxy::$enabled = false;
817
818
		$versionStatus = \OCP\App::isEnabled('files_versions');
819
		\OC_App::disable('files_versions');
820
821
		$encryptedFiles = array();
822
823
		// Encrypt unencrypted files
824
		foreach ($found['plain'] as $plainFile) {
825
826
			//get file info
827
			$fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']);
828
829
			//relative to data/<user>/file
830
			$relPath = $plainFile['path'];
831
832
			//relative to /data
833
			$rawPath = '/' . $this->userId . '/files/' . $plainFile['path'];
834
835
			// keep timestamp
836
			$timestamp = $fileInfo['mtime'];
837
838
			// Open plain file handle for binary reading
839
			$plainHandle = $this->view->fopen($rawPath, 'rb');
840
841
			// Open enc file handle for binary writing, with same filename as original plain file
842
			$encHandle = fopen('crypt://' . $rawPath . '.part', 'wb');
843
844
			if (is_resource($encHandle) && is_resource($plainHandle)) {
845
				// Move plain file to a temporary location
846
				$size = stream_copy_to_stream($plainHandle, $encHandle);
847
848
				fclose($encHandle);
849
				fclose($plainHandle);
850
851
				$fakeRoot = $this->view->getRoot();
852
				$this->view->chroot('/' . $this->userId . '/files');
853
854
				$this->view->rename($relPath . '.part', $relPath);
855
856
				// set timestamp
857
				$this->view->touch($relPath, $timestamp);
858
859
				$encSize = $this->view->filesize($relPath);
860
861
				$this->view->chroot($fakeRoot);
862
863
				// Add the file to the cache
864
				\OC\Files\Filesystem::putFileInfo($relPath, array(
865
					'encrypted' => true,
866
					'size' => $encSize,
867
					'unencrypted_size' => $size,
868
					'etag' => $fileInfo['etag']
869
				));
870
871
				$encryptedFiles[] = $relPath;
872
			} else {
873
				\OCP\Util::writeLog('files_encryption', 'initial encryption: could not encrypt ' . $rawPath, \OCP\Util::FATAL);
874
				$result = false;
875
			}
876
		}
877
878
		\OC_FileProxy::$enabled = true;
879
880
		if ($versionStatus) {
881
			\OC_App::enable('files_versions');
882
		}
883
884
		$result = $result && $this->encryptVersions($encryptedFiles);
885
886
		return $result;
887
888
	}
889
890
	/**
891
	 * Return important encryption related paths
892
	 * @param string $pathName Name of the directory to return the path of
893
	 * @return string path
894
	 */
895
	public function getPath($pathName) {
896
897
		switch ($pathName) {
898
899
			case 'publicKeyDir':
900
901
				return $this->publicKeyDir;
902
903
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
904
905
			case 'encryptionDir':
906
907
				return $this->encryptionDir;
908
909
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
910
911
			case 'keysPath':
912
913
				return $this->keysPath;
914
915
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
916
917
			case 'publicKeyPath':
918
919
				return $this->publicKeyPath;
920
921
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
922
923
			case 'privateKeyPath':
924
925
				return $this->privateKeyPath;
926
927
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
928
		}
929
930
		return false;
931
932
	}
933
934
	/**
935
	 * Returns whether the given user is ready for encryption.
936
	 * Also returns true if the given user is the public user
937
	 * or the recovery key user.
938
	 *
939
	 * @param string $user user to check
940
	 *
941
	 * @return boolean true if the user is ready, false otherwise
942
	 */
943
	private function isUserReady($user) {
944
		if ($user === $this->publicShareKeyId
945
			|| $user === $this->recoveryKeyId
946
		) {
947
			return true;
948
		}
949
		try {
950
			$util = new Util($this->view, $user);
951
			return $util->ready();
952
		} catch (NoUserException $e) {
953
			\OCP\Util::writeLog('Encryption library',
954
				'No User object for '.$user, \OCP\Util::DEBUG);
955
			return false;
956
		}
957
	}
958
959
	/**
960
	 * Filter an array of UIDs to return only ones ready for sharing
961
	 * @param array $unfilteredUsers users to be checked for sharing readiness
962
	 * @return array as multi-dimensional array. keys: ready, unready
963
	 */
964
	public function filterShareReadyUsers($unfilteredUsers) {
965
966
		// This array will collect the filtered IDs
967
		$readyIds = $unreadyIds = array();
968
969
		// Loop through users and create array of UIDs that need new keyfiles
970
		foreach ($unfilteredUsers as $user) {
971
			// Check that the user is encryption capable, or is the
972
			// public system user (for public shares)
973
			if ($this->isUserReady($user)) {
974
975
				// Construct array of ready UIDs for Keymanager{}
976
				$readyIds[] = $user;
977
978
			} else {
979
980
				// Construct array of unready UIDs for Keymanager{}
981
				$unreadyIds[] = $user;
982
983
				// Log warning; we can't do necessary setup here
984
				// because we don't have the user passphrase
985
				\OCP\Util::writeLog('Encryption library',
986
					'"' . $user . '" is not setup for encryption', \OCP\Util::WARN);
987
988
			}
989
990
		}
991
992
		return array(
993
			'ready' => $readyIds,
994
			'unready' => $unreadyIds
995
		);
996
997
	}
998
999
	/**
1000
	 * Decrypt a keyfile
1001
	 * @param string $filePath
1002
	 * @param string $privateKey
1003
	 * @return false|string
1004
	 */
1005
	private function decryptKeyfile($filePath, $privateKey) {
1006
1007
		// Get the encrypted keyfile
1008
		$encKeyfile = Keymanager::getFileKey($this->view, $this, $filePath);
1009
1010
		// The file has a shareKey and must use it for decryption
1011
		$shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $filePath);
1012
1013
		$plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
0 ignored issues
show
Bug introduced by
It seems like $encKeyfile defined by \OCA\Files_Encryption\Ke...view, $this, $filePath) on line 1008 can also be of type boolean; however, OCA\Files_Encryption\Crypt::multiKeyDecrypt() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $shareKey defined by \OCA\Files_Encryption\Ke...eyId, $this, $filePath) on line 1011 can also be of type boolean; however, OCA\Files_Encryption\Crypt::multiKeyDecrypt() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1014
1015
		return $plainKeyfile;
1016
	}
1017
1018
	/**
1019
	 * Encrypt keyfile to multiple users
1020
	 * @param Session $session
1021
	 * @param array $users list of users which should be able to access the file
1022
	 * @param string $filePath path of the file to be shared
1023
	 * @return bool
1024
	 */
1025
	public function setSharedFileKeyfiles(Session $session, array $users, $filePath) {
1026
1027
		// Make sure users are capable of sharing
1028
		$filteredUids = $this->filterShareReadyUsers($users);
1029
1030
		// If we're attempting to share to unready users
1031
		if (!empty($filteredUids['unready'])) {
1032
1033
			\OCP\Util::writeLog('Encryption library',
1034
				'Sharing to these user(s) failed as they are unready for encryption:"'
1035
				. print_r($filteredUids['unready'], 1), \OCP\Util::WARN);
1036
1037
			return false;
1038
1039
		}
1040
1041
		// Get public keys for each user, ready for generating sharekeys
1042
		$userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']);
1043
1044
		// Note proxy status then disable it
1045
		$proxyStatus = \OC_FileProxy::$enabled;
1046
		\OC_FileProxy::$enabled = false;
1047
1048
		// Get the current users's private key for decrypting existing keyfile
1049
		$privateKey = $session->getPrivateKey();
1050
1051
		try {
1052
			// Decrypt keyfile
1053
			$plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
1054
			// Re-enc keyfile to (additional) sharekeys
1055
			$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
1056
		} catch (Exception\EncryptionException $e) {
1057
			$msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage();
1058
			\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
1059
			return false;
1060
		} catch (\Exception $e) {
1061
			$msg = 'set shareFileKeyFailed (unknown error): ' . $e->getMessage();
1062
			\OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL);
1063
			return false;
1064
		}
1065
1066
		// Save the recrypted key to it's owner's keyfiles directory
1067
		// Save new sharekeys to all necessary user directory
1068
		if (
1069
				!Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data'])
1070
				|| !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys'])
1071
		) {
1072
1073
			\OCP\Util::writeLog('Encryption library',
1074
				'Keyfiles could not be saved for users sharing ' . $filePath, \OCP\Util::ERROR);
1075
1076
			return false;
1077
1078
		}
1079
1080
		// Return proxy to original status
1081
		\OC_FileProxy::$enabled = $proxyStatus;
1082
1083
		return true;
1084
	}
1085
1086
	/**
1087
	 * Find, sanitise and format users sharing a file
1088
	 * @note This wraps other methods into a portable bundle
1089
	 * @param boolean $sharingEnabled
1090
	 * @param string $filePath path relativ to current users files folder
1091
	 */
1092
	public function getSharingUsersArray($sharingEnabled, $filePath) {
1093
1094
		$appConfig = \OC::$server->getAppConfig();
1095
1096
		// Check if key recovery is enabled
1097
		if (
1098
			$appConfig->getValue('files_encryption', 'recoveryAdminEnabled')
1099
			&& $this->recoveryEnabledForUser()
1100
		) {
1101
			$recoveryEnabled = true;
1102
		} else {
1103
			$recoveryEnabled = false;
1104
		}
1105
1106
		// Make sure that a share key is generated for the owner too
1107
		list($owner, $ownerPath) = $this->getUidAndFilename($filePath);
1108
1109
		$ownerPath = Helper::stripPartialFileExtension($ownerPath);
1110
1111
		// always add owner to the list of users with access to the file
1112
		$userIds = array($owner);
1113
1114
		if ($sharingEnabled) {
1115
1116
			// Find out who, if anyone, is sharing the file
1117
			$result = \OCP\Share::getUsersSharingFile($ownerPath, $owner);
0 ignored issues
show
Bug introduced by
It seems like $owner defined by $this->getUidAndFilename($filePath) on line 1107 can also be of type false or null; however, OCP\Share::getUsersSharingFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1118
			$userIds = \array_merge($userIds, $result['users']);
1119
			if ($result['public'] || $result['remote']) {
1120
				$userIds[] = $this->publicShareKeyId;
1121
			}
1122
1123
		}
1124
1125
		// If recovery is enabled, add the
1126
		// Admin UID to list of users to share to
1127
		if ($recoveryEnabled) {
1128
			// Find recoveryAdmin user ID
1129
			$recoveryKeyId = $appConfig->getValue('files_encryption', 'recoveryKeyId');
1130
			// Add recoveryAdmin to list of users sharing
1131
			$userIds[] = $recoveryKeyId;
1132
		}
1133
1134
		// check if it is a group mount
1135
		if (\OCP\App::isEnabled("files_external")) {
1136
			$mounts = \OC_Mount_Config::getSystemMountPoints();
1137
			foreach ($mounts as $mount) {
1138
				if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) {
1139
					$userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']));
1140
				}
1141
			}
1142
		}
1143
1144
		// Remove duplicate UIDs
1145
		$uniqueUserIds = array_unique($userIds);
1146
1147
		return $uniqueUserIds;
1148
1149
	}
1150
1151
	private function getUserWithAccessToMountPoint($users, $groups) {
1152
		$result = array();
1153
		if (in_array('all', $users)) {
1154
			$result = \OCP\User::getUsers();
1155
		} else {
1156
			$result = array_merge($result, $users);
1157
			foreach ($groups as $group) {
1158
				$result = array_merge($result, \OC_Group::usersInGroup($group));
1159
			}
1160
		}
1161
1162
		return $result;
1163
	}
1164
1165
	/**
1166
	 * set migration status
1167
	 * @param int $status
1168
	 * @param int $preCondition only update migration status if the previous value equals $preCondition
1169
	 * @return boolean
1170
	 */
1171
	private function setMigrationStatus($status, $preCondition = null) {
1172
1173
		// convert to string if preCondition is set
1174
		$preCondition = ($preCondition === null) ? null : (string)$preCondition;
1175
1176
		try {
1177
			\OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)$status, $preCondition);
1178
			return true;
1179
		} catch(\OCP\PreConditionNotMetException $e) {
1180
			return false;
1181
		}
1182
1183
	}
1184
1185
	/**
1186
	 * start migration mode to initially encrypt users data
1187
	 * @return boolean
1188
	 */
1189 View Code Duplication
	public function beginMigration() {
1190
1191
		$result = $this->setMigrationStatus(self::MIGRATION_IN_PROGRESS, self::MIGRATION_OPEN);
1192
1193
		if ($result) {
1194
			\OCP\Util::writeLog('Encryption library', "Start migration to encryption mode for " . $this->userId, \OCP\Util::INFO);
1195
		} else {
1196
			\OCP\Util::writeLog('Encryption library', "Could not activate migration mode for " . $this->userId . ". Probably another process already started the initial encryption", \OCP\Util::WARN);
1197
		}
1198
1199
		return $result;
1200
	}
1201
1202
	public function resetMigrationStatus() {
1203
		return $this->setMigrationStatus(self::MIGRATION_OPEN);
1204
1205
	}
1206
1207
	/**
1208
	 * close migration mode after users data has been encrypted successfully
1209
	 * @return boolean
1210
	 */
1211 View Code Duplication
	public function finishMigration() {
1212
		$result = $this->setMigrationStatus(self::MIGRATION_COMPLETED);
1213
1214
		if ($result) {
1215
			\OCP\Util::writeLog('Encryption library', "Finish migration successfully for " . $this->userId, \OCP\Util::INFO);
1216
		} else {
1217
			\OCP\Util::writeLog('Encryption library', "Could not deactivate migration mode for " . $this->userId, \OCP\Util::WARN);
1218
		}
1219
1220
		return $result;
1221
	}
1222
1223
	/**
1224
	 * check if files are already migrated to the encryption system
1225
	 * @return int|false migration status, false = in case of no record
1226
	 * @note If records are not being returned, check for a hidden space
1227
	 *       at the start of the uid in db
1228
	 */
1229
	public function getMigrationStatus() {
1230
1231
		$migrationStatus = false;
1232
		if (\OCP\User::userExists($this->userId)) {
1233
			$migrationStatus = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'migration_status', null);
1234
			if ($migrationStatus === null) {
1235
				\OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)self::MIGRATION_OPEN);
1236
				$migrationStatus = self::MIGRATION_OPEN;
1237
			}
1238
		}
1239
1240
		return (int)$migrationStatus;
1241
1242
	}
1243
1244
	/**
1245
	 * get uid of the owners of the file and the path to the file
1246
	 * @param string $path Path of the file to check
1247
	 * @throws \Exception
1248
	 * @note $shareFilePath must be relative to data/UID/files. Files
1249
	 *       relative to /Shared are also acceptable
1250
	 * @return array
1251
	 */
1252
	public function getUidAndFilename($path) {
1253
1254
		$pathinfo = pathinfo($path);
1255
		$partfile = false;
1256
		$parentFolder = false;
1257
		if (array_key_exists('extension', $pathinfo) && $pathinfo['extension'] === 'part') {
1258
			// if the real file exists we check this file
1259
			$filePath = $this->userFilesDir . '/' .$pathinfo['dirname'] . '/' . $pathinfo['filename'];
1260
			if ($this->view->file_exists($filePath)) {
1261
				$pathToCheck = $pathinfo['dirname'] . '/' . $pathinfo['filename'];
1262
			} else { // otherwise we look for the parent
1263
				$pathToCheck = $pathinfo['dirname'];
1264
				$parentFolder = true;
1265
			}
1266
			$partfile = true;
1267
		} else {
1268
			$pathToCheck = $path;
1269
		}
1270
1271
		$view = new \OC\Files\View($this->userFilesDir);
1272
		$fileOwnerUid = $view->getOwner($pathToCheck);
1273
1274
		// handle public access
1275
		if ($this->isPublic) {
1276
			return array($this->userId, $path);
1277
		} else {
1278
1279
			// Check that UID is valid
1280
			if (!\OCP\User::userExists($fileOwnerUid)) {
0 ignored issues
show
Bug introduced by
It seems like $fileOwnerUid defined by $view->getOwner($pathToCheck) on line 1272 can also be of type false or null; however, OCP\User::userExists() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1281
				throw new \Exception(
1282
					'Could not find owner (UID = "' . var_export($fileOwnerUid, 1) . '") of file "' . $path . '"');
1283
			}
1284
1285
			// NOTE: Bah, this dependency should be elsewhere
1286
			\OC\Files\Filesystem::initMountPoints($fileOwnerUid);
0 ignored issues
show
Bug introduced by
It seems like $fileOwnerUid defined by $view->getOwner($pathToCheck) on line 1272 can also be of type false or null; however, OC\Files\Filesystem::initMountPoints() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1287
1288
			// If the file owner is the currently logged in user
1289
			if ($fileOwnerUid === $this->userId) {
1290
1291
				// Assume the path supplied is correct
1292
				$filename = $path;
1293
1294
			} else {
1295
				$info = $view->getFileInfo($pathToCheck);
1296
				$ownerView = new \OC\Files\View('/' . $fileOwnerUid . '/files');
1297
1298
				// Fetch real file path from DB
1299
				$filename = $ownerView->getPath($info['fileid']);
1300
				if ($parentFolder) {
1301
					$filename = $filename . '/'. $pathinfo['filename'];
1302
				}
1303
1304
				if ($partfile) {
1305
					$filename = $filename . '.' . $pathinfo['extension'];
1306
				}
1307
1308
			}
1309
1310
			return array(
1311
				$fileOwnerUid,
1312
				\OC\Files\Filesystem::normalizePath($filename)
1313
			);
1314
		}
1315
	}
1316
1317
	/**
1318
	 * go recursively through a dir and collect all files and sub files.
1319
	 * @param string $dir relative to the users files folder
1320
	 * @return array with list of files relative to the users files folder
1321
	 */
1322
	public function getAllFiles($dir, $mountPoint = '') {
1323
		$result = array();
1324
		$dirList = array($dir);
1325
1326
		while ($dirList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dirList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1327
			$dir = array_pop($dirList);
1328
			$content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath(
1329
					$this->userFilesDir . '/' . $dir));
1330
1331
			foreach ($content as $c) {
1332
				// getDirectoryContent() returns the paths relative to the mount points, so we need
1333
				// to re-construct the complete path
1334
				$path = ($mountPoint !== '') ? $mountPoint . '/' .  $c['path'] : $c['path'];
1335
				$path = \OC\Files\Filesystem::normalizePath($path);
1336
				if ($c['type'] === 'dir') {
1337
					$dirList[] = substr($path, strlen('/' . \OCP\User::getUser() . "/files"));
1338
				} else {
1339
					$result[] = substr($path, strlen('/' . \OCP\User::getUser() . "/files"));
1340
				}
1341
			}
1342
1343
		}
1344
1345
		return $result;
1346
	}
1347
1348
	/**
1349
	 * get owner of the shared files.
1350
	 * @param int $id ID of a share
1351
	 * @return string owner
1352
	 */
1353
	public function getOwnerFromSharedFile($id) {
1354
1355
		$query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
1356
1357
		$result = $query->execute(array($id));
1358
1359
		$source = null;
1360
		if (\OCP\DB::isError($result)) {
1361
			\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
1362
		} else {
1363
			$source = $result->fetchRow();
1364
		}
1365
1366
		$fileOwner = false;
1367
1368
		if ($source && isset($source['parent'])) {
1369
1370
			$parent = $source['parent'];
1371
1372
			while (isset($parent)) {
1373
1374
				$query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
1375
1376
				$result = $query->execute(array($parent));
1377
1378
				$item = null;
1379
				if (\OCP\DB::isError($result)) {
1380
					\OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
1381
				} else {
1382
					$item = $result->fetchRow();
1383
				}
1384
1385
				if ($item && isset($item['parent'])) {
1386
1387
					$parent = $item['parent'];
1388
1389
				} else {
1390
1391
					$fileOwner = $item['uid_owner'];
1392
1393
					break;
1394
1395
				}
1396
			}
1397
1398
		} else {
1399
1400
			$fileOwner = $source['uid_owner'];
1401
1402
		}
1403
1404
		return $fileOwner;
1405
1406
	}
1407
1408
	/**
1409
	 * @return string
1410
	 */
1411
	public function getUserId() {
1412
		return $this->userId;
1413
	}
1414
1415
	/**
1416
	 * @return string
1417
	 */
1418
	public function getKeyId() {
1419
		return $this->keyId;
1420
	}
1421
1422
	/**
1423
	 * @return string
1424
	 */
1425
	public function getUserFilesDir() {
1426
		return $this->userFilesDir;
1427
	}
1428
1429
	/**
1430
	 * @param string $password
1431
	 * @return bool
1432
	 */
1433
	public function checkRecoveryPassword($password) {
1434
1435
		$result = false;
1436
1437
		$recoveryKey = Keymanager::getPrivateSystemKey($this->recoveryKeyId);
1438
		$decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password);
0 ignored issues
show
Bug introduced by
It seems like $recoveryKey defined by \OCA\Files_Encryption\Ke...y($this->recoveryKeyId) on line 1437 can also be of type boolean; however, OCA\Files_Encryption\Crypt::decryptPrivateKey() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1439
1440
		if ($decryptedRecoveryKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $decryptedRecoveryKey of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1441
			$result = true;
1442
		}
1443
1444
		return $result;
1445
	}
1446
1447
	/**
1448
	 * @return string
1449
	 */
1450
	public function getRecoveryKeyId() {
1451
		return $this->recoveryKeyId;
1452
	}
1453
1454
	/**
1455
	 * add recovery key to all encrypted files
1456
	 */
1457
	public function addRecoveryKeys($path = '/') {
1458
		$dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path);
1459
		foreach ($dirContent as $item) {
1460
			// get relative path from files_encryption/keyfiles/
1461
			$filePath = substr($item['path'], strlen('files_encryption/keys'));
1462
			if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) {
1463
				$this->addRecoveryKeys($filePath . '/');
1464
			} else {
1465
				$session = new Session(new \OC\Files\View('/'));
1466
				$sharingEnabled = \OCP\Share::isEnabled();
1467
				$usersSharing = $this->getSharingUsersArray($sharingEnabled, $filePath);
1468
				$this->setSharedFileKeyfiles($session, $usersSharing, $filePath);
1469
			}
1470
		}
1471
	}
1472
1473
	/**
1474
	 * remove recovery key to all encrypted files
1475
	 */
1476
	public function removeRecoveryKeys($path = '/') {
1477
		$dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path);
1478
		foreach ($dirContent as $item) {
1479
			// get relative path from files_encryption/keyfiles
1480
			$filePath = substr($item['path'], strlen('files_encryption/keys'));
1481
			if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) {
1482
				$this->removeRecoveryKeys($filePath . '/');
1483
			} else {
1484
				$this->view->unlink($this->keysPath . '/' . $filePath . '/' . $this->recoveryKeyId . '.shareKey');
1485
			}
1486
		}
1487
	}
1488
1489
	/**
1490
	 * decrypt given file with recovery key and encrypt it again to the owner and his new key
1491
	 * @param string $file
1492
	 * @param string $privateKey recovery key to decrypt the file
1493
	 */
1494
	private function recoverFile($file, $privateKey) {
1495
1496
		$sharingEnabled = \OCP\Share::isEnabled();
1497
1498
		// Find out who, if anyone, is sharing the file
1499
		if ($sharingEnabled) {
1500
			$result = \OCP\Share::getUsersSharingFile($file, $this->userId, true);
1501
			$userIds = $result['users'];
1502
			$userIds[] = $this->recoveryKeyId;
1503
			if ($result['public']) {
1504
				$userIds[] = $this->publicShareKeyId;
1505
			}
1506
		} else {
1507
			$userIds = array(
1508
				$this->userId,
1509
				$this->recoveryKeyId
1510
			);
1511
		}
1512
		$filteredUids = $this->filterShareReadyUsers($userIds);
1513
1514
		//decrypt file key
1515
		$encKeyfile = Keymanager::getFileKey($this->view, $this, $file);
1516
		$shareKey = Keymanager::getShareKey($this->view, $this->recoveryKeyId, $this, $file);
1517
		$plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
0 ignored issues
show
Bug introduced by
It seems like $encKeyfile defined by \OCA\Files_Encryption\Ke...is->view, $this, $file) on line 1515 can also be of type boolean; however, OCA\Files_Encryption\Crypt::multiKeyDecrypt() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $shareKey defined by \OCA\Files_Encryption\Ke...eryKeyId, $this, $file) on line 1516 can also be of type boolean; however, OCA\Files_Encryption\Crypt::multiKeyDecrypt() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1518
		// encrypt file key again to all users, this time with the new public key for the recovered use
1519
		$userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']);
1520
		$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
1521
1522
		Keymanager::setFileKey($this->view, $this, $file, $multiEncKey['data']);
1523
		Keymanager::setShareKeys($this->view, $this, $file, $multiEncKey['keys']);
1524
1525
	}
1526
1527
	/**
1528
	 * collect all files and recover them one by one
1529
	 * @param string $path to look for files keys
1530
	 * @param string $privateKey private recovery key which is used to decrypt the files
1531
	 */
1532
	private function recoverAllFiles($path, $privateKey) {
1533
		$dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path);
1534
		foreach ($dirContent as $item) {
1535
			// get relative path from files_encryption/keyfiles
1536
			$filePath = substr($item['path'], strlen('files_encryption/keys'));
1537
			if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) {
1538
				$this->recoverAllFiles($filePath . '/', $privateKey);
1539
			} else {
1540
				$this->recoverFile($filePath, $privateKey);
1541
			}
1542
		}
1543
	}
1544
1545
	/**
1546
	 * recover users files in case of password lost
1547
	 * @param string $recoveryPassword
1548
	 */
1549
	public function recoverUsersFiles($recoveryPassword) {
1550
1551
		$encryptedKey = Keymanager::getPrivateSystemKey( $this->recoveryKeyId);
1552
		$privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword);
0 ignored issues
show
Bug introduced by
It seems like $encryptedKey defined by \OCA\Files_Encryption\Ke...y($this->recoveryKeyId) on line 1551 can also be of type boolean; however, OCA\Files_Encryption\Crypt::decryptPrivateKey() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1553
1554
		$this->recoverAllFiles('/', $privateKey);
0 ignored issues
show
Security Bug introduced by
It seems like $privateKey defined by \OCA\Files_Encryption\Cr...Key, $recoveryPassword) on line 1552 can also be of type false; however, OCA\Files_Encryption\Util::recoverAllFiles() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1555
	}
1556
1557
	/**
1558
	 * create a backup of all keys from the user
1559
	 *
1560
	 * @param string $purpose define the purpose of the backup, will be part of the backup folder name
1561
	 * @param boolean $timestamp (optional) should a timestamp be added, default true
1562
	 * @param boolean $includeUserKeys (optional) include users private-/public-key, default true
1563
	 */
1564
	public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
1565
		$this->userId;
1566
		$backupDir = $this->encryptionDir . '/backup.' . $purpose;
1567
		$backupDir .= ($timestamp) ? '.' . date("Y-m-d_H-i-s") . '/' : '/';
1568
		$this->view->mkdir($backupDir);
1569
		$this->view->copy($this->keysPath, $backupDir . 'keys/');
1570
		if ($includeUserKeys) {
1571
			$this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.privateKey');
1572
			$this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.publicKey');
1573
		}
1574
	}
1575
1576
	/**
1577
	 * restore backup
1578
	 *
1579
	 * @param string $backup complete name of the backup
1580
	 * @return boolean
1581
	 */
1582
	public function restoreBackup($backup) {
1583
		$backupDir = $this->encryptionDir . '/backup.' . $backup . '/';
1584
1585
		$fileKeysRestored = $this->view->rename($backupDir . 'keys', $this->encryptionDir . '/keys');
1586
1587
		$pubKeyRestored = $privKeyRestored = true;
1588
		if (
1589
			$this->view->file_exists($backupDir . $this->userId . '.privateKey') &&
1590
			$this->view->file_exists($backupDir . $this->userId . '.privateKey')
1591
		) {
1592
1593
			$pubKeyRestored = $this->view->rename($backupDir . $this->userId . '.publicKey', $this->publicKeyPath);
1594
			$privKeyRestored = $this->view->rename($backupDir . $this->userId . '.privateKey', $this->privateKeyPath);
1595
		}
1596
1597
		if ($fileKeysRestored && $pubKeyRestored && $privKeyRestored) {
1598
			$this->view->deleteAll($backupDir);
1599
1600
			return true;
1601
		}
1602
1603
		return false;
1604
	}
1605
1606
	/**
1607
	 * delete backup
1608
	 *
1609
	 * @param string $backup complete name of the backup
1610
	 * @return boolean
1611
	 */
1612
	public function deleteBackup($backup) {
1613
		$backupDir = $this->encryptionDir . '/backup.' . $backup . '/';
1614
		return $this->view->deleteAll($backupDir);
1615
	}
1616
1617
	/**
1618
	 * check if the file is stored on a system wide mount point
1619
	 * @param string $path relative to /data/user with leading '/'
1620
	 * @param string $uid
1621
	 * @return boolean
1622
	 */
1623
	public function isSystemWideMountPoint($path, $uid) {
1624
		$normalizedPath = ltrim($path, '/');
1625
		if (\OCP\App::isEnabled("files_external")) {
1626
			$mounts = \OC_Mount_Config::getSystemMountPoints();
1627
			foreach ($mounts as $mount) {
1628
				if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) {
1629
					if ($this->isMountPointApplicableToUser($mount, $uid)) {
1630
						return true;
1631
					}
1632
				}
1633
			}
1634
		}
1635
		return false;
1636
	}
1637
1638
	/**
1639
	 * check if mount point is applicable to user
1640
	 *
1641
	 * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
1642
	 * @param string $uid
1643
	 * @return boolean
1644
	 */
1645
	protected function isMountPointApplicableToUser($mount, $uid) {
1646
		$acceptedUids = array('all', $uid);
1647
		// check if mount point is applicable for the user
1648
		$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
1649
		if (!empty($intersection)) {
1650
			return true;
1651
		}
1652
		// check if mount point is applicable for group where the user is a member
1653
		foreach ($mount['applicable']['groups'] as $gid) {
1654
			if (\OC_Group::inGroup($uid, $gid)) {
1655
				return true;
1656
			}
1657
		}
1658
		return false;
1659
	}
1660
1661
	/**
1662
	 * decrypt private key and add it to the current session
1663
	 * @param array $params with 'uid' and 'password'
1664
	 * @return mixed session or false
1665
	 */
1666
	public function initEncryption($params) {
1667
1668
		$session = new Session($this->view);
1669
1670
		// we tried to initialize the encryption app for this session
1671
		$session->setInitialized(Session::INIT_EXECUTED);
1672
1673
		$encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']);
1674
1675
		$privateKey = false;
1676
		if ($encryptedKey) {
1677
			$privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']);
0 ignored issues
show
Bug introduced by
It seems like $encryptedKey defined by \OCA\Files_Encryption\Ke...->view, $params['uid']) on line 1673 can also be of type boolean; however, OCA\Files_Encryption\Crypt::decryptPrivateKey() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1678
		}
1679
1680
		if ($privateKey === false) {
1681
			\OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid']
1682
					. '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access', \OCP\Util::ERROR);
1683
			return false;
1684
		}
1685
1686
		$session->setPrivateKey($privateKey);
1687
		$session->setInitialized(Session::INIT_SUCCESSFUL);
1688
1689
		return $session;
1690
	}
1691
1692
	/*
1693
	 * remove encryption related keys from the session
1694
	 */
1695
	public function closeEncryptionSession() {
1696
		$session = new Session($this->view);
1697
		$session->closeSession();
1698
	}
1699
1700
}
1701