Blame view

sources/apps/files_encryption/lib/proxy.php 10.4 KB
03e52840d   Kload   Init
1
2
3
4
5
  <?php
  
  /**
   * ownCloud
   *
6d9380f96   Cédric Dupont   Update sources OC...
6
7
8
9
   * @author Bjoern Schiessle, Sam Tuke, Robin Appelman
   * @copyright 2012 Sam Tuke <samtuke@owncloud.com>
   *            2012 Robin Appelman <icewind1991@gmail.com>
   *            2014 Bjoern Schiessle <schiessle@owncloud.com>
03e52840d   Kload   Init
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
   * License as published by the Free Software Foundation; either
   * version 3 of the License, or any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
   *
   * You should have received a copy of the GNU Affero General Public
   * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
   *
   */
  
  /**
6d9380f96   Cédric Dupont   Update sources OC...
27
   * Encryption proxy which handles filesystem operations before and after
03e52840d   Kload   Init
28
29
30
31
32
33
34
35
36
37
38
   *        execution and encrypts, and handles keyfiles accordingly. Used for
   *        webui.
   */
  
  namespace OCA\Encryption;
  
  /**
   * Class Proxy
   * @package OCA\Encryption
   */
  class Proxy extends \OC_FileProxy {
a293d369c   Kload   Update sources to...
39
40
  	private static $unencryptedSizes = array(); // remember unencrypted size
  	private static $fopenMode = array(); // remember the fopen mode
6d9380f96   Cédric Dupont   Update sources OC...
41
  	private static $enableEncryption = false; // Enable encryption for the given path
03e52840d   Kload   Init
42
43
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
44
  	 * check if path is excluded from encryption
03e52840d   Kload   Init
45
  	 *
6d9380f96   Cédric Dupont   Update sources OC...
46
47
48
  	 * @param string $path relative to data/
  	 * @param string $uid user
  	 * @return boolean
03e52840d   Kload   Init
49
  	 */
6d9380f96   Cédric Dupont   Update sources OC...
50
  	private function isExcludedPath($path, $uid) {
03e52840d   Kload   Init
51

6d9380f96   Cédric Dupont   Update sources OC...
52
  		$view = new \OC\Files\View();
31b7f2792   Kload   Upgrade to ownclo...
53

6d9380f96   Cédric Dupont   Update sources OC...
54
55
56
  		// files outside of the files-folder are excluded
  		if(strpos($path, '/' . $uid . '/files') !== 0) {
  			return true;
03e52840d   Kload   Init
57
  		}
6d9380f96   Cédric Dupont   Update sources OC...
58
59
  		if (!$view->file_exists($path)) {
  			$path = dirname($path);
03e52840d   Kload   Init
60
  		}
6d9380f96   Cédric Dupont   Update sources OC...
61
62
63
64
65
66
  		// we don't encrypt server-to-server shares
  		list($storage, ) = \OC\Files\Filesystem::resolvePath($path);
  		/**
  		 * @var \OCP\Files\Storage $storage
  		 */
  		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
03e52840d   Kload   Init
67
68
  			return true;
  		}
6d9380f96   Cédric Dupont   Update sources OC...
69
70
71
72
73
74
75
76
77
78
79
80
81
  		return false;
  	}
  
  	/**
  	 * Check if a file requires encryption
  	 * @param string $path
  	 * @param string $mode type of access
  	 * @return bool
  	 *
  	 * Tests if server side encryption is enabled, and if we should call the
  	 * crypt stream wrapper for the given file
  	 */
  	private function shouldEncrypt($path, $mode = 'w') {
03e52840d   Kload   Init
82

6d9380f96   Cédric Dupont   Update sources OC...
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  		$userId = Helper::getUser($path);
  		$session = new Session(new \OC\Files\View());
  
  		// don't call the crypt stream wrapper, if...
  		if (
  				$session->getInitialized() !== Session::INIT_SUCCESSFUL // encryption successful initialized
  				|| Crypt::mode() !== 'server'   // we are not in server-side-encryption mode
  				|| $this->isExcludedPath($path, $userId) // if path is excluded from encryption
  				|| substr($path, 0, 8) === 'crypt://' // we are already in crypt mode
  		) {
  			return false;
  		}
  
  		$view = new \OC\Files\View('');
  		$util = new Util($view, $userId);
  
  		// for write operation we always encrypt the files, for read operations
  		// we check if the existing file is encrypted or not decide if it needs to
  		// decrypt it.
  		if (($mode !== 'r' && $mode !== 'rb') || $util->isEncryptedPath($path)) {
03e52840d   Kload   Init
103
104
105
106
107
108
109
  			return true;
  		}
  
  		return false;
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
110
111
  	 * @param string $path
  	 * @param string $data
03e52840d   Kload   Init
112
113
114
  	 * @return bool
  	 */
  	public function preFile_put_contents($path, &$data) {
6d9380f96   Cédric Dupont   Update sources OC...
115
  		if ($this->shouldEncrypt($path)) {
03e52840d   Kload   Init
116
117
118
119
  
  			if (!is_resource($data)) {
  
  				// get root view
6d9380f96   Cédric Dupont   Update sources OC...
120
  				$view = new \OC\Files\View('/');
03e52840d   Kload   Init
121
122
123
124
125
126
127
  
  				// get relative path
  				$relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  
  				if (!isset($relativePath)) {
  					return true;
  				}
31b7f2792   Kload   Upgrade to ownclo...
128
129
  				// create random cache folder
  				$cacheFolder = rand();
6d9380f96   Cédric Dupont   Update sources OC...
130
  				$path_slices = explode('/', \OC\Files\Filesystem::normalizePath($path));
31b7f2792   Kload   Upgrade to ownclo...
131
132
133
134
  				$path_slices[2] = "cache/".$cacheFolder;
  				$tmpPath = implode('/', $path_slices);
  
  				$handle = fopen('crypt://' . $tmpPath, 'w');
03e52840d   Kload   Init
135
136
137
138
139
140
141
142
143
144
145
146
147
  				if (is_resource($handle)) {
  
  					// write data to stream
  					fwrite($handle, $data);
  
  					// close stream
  					fclose($handle);
  
  					// disable encryption proxy to prevent recursive calls
  					$proxyStatus = \OC_FileProxy::$enabled;
  					\OC_FileProxy::$enabled = false;
  
  					// get encrypted content
31b7f2792   Kload   Upgrade to ownclo...
148
  					$data = $view->file_get_contents($tmpPath);
03e52840d   Kload   Init
149

a293d369c   Kload   Update sources to...
150
151
152
153
  					// store new unenecrypted size so that it can be updated
  					// in the post proxy
  					$tmpFileInfo = $view->getFileInfo($tmpPath);
  					if ( isset($tmpFileInfo['size']) ) {
6d9380f96   Cédric Dupont   Update sources OC...
154
  						self::$unencryptedSizes[\OC\Files\Filesystem::normalizePath($path)] = $tmpFileInfo['size'];
a293d369c   Kload   Update sources to...
155
  					}
03e52840d   Kload   Init
156
  					// remove our temp file
31b7f2792   Kload   Upgrade to ownclo...
157
  					$view->deleteAll('/' . \OCP\User::getUser() . '/cache/' . $cacheFolder);
03e52840d   Kload   Init
158
159
160
  
  					// re-enable proxy - our work is done
  					\OC_FileProxy::$enabled = $proxyStatus;
837968727   Kload   [enh] Upgrade to ...
161
162
  				} else {
  					return false;
03e52840d   Kload   Init
163
164
165
166
167
168
169
170
171
  				}
  			}
  		}
  
  		return true;
  
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
172
  	 * update file cache with the new unencrypted size after file was written
a293d369c   Kload   Update sources to...
173
174
175
176
177
  	 * @param string $path
  	 * @param mixed $result
  	 * @return mixed
  	 */
  	public function postFile_put_contents($path, $result) {
6d9380f96   Cédric Dupont   Update sources OC...
178
  		$normalizedPath = \OC\Files\Filesystem::normalizePath($path);
a293d369c   Kload   Update sources to...
179
  		if ( isset(self::$unencryptedSizes[$normalizedPath]) ) {
6d9380f96   Cédric Dupont   Update sources OC...
180
  			$view = new \OC\Files\View('/');
a293d369c   Kload   Update sources to...
181
182
183
184
185
186
187
188
189
  			$view->putFileInfo($normalizedPath,
  					array('encrypted' => true, 'unencrypted_size' => self::$unencryptedSizes[$normalizedPath]));
  			unset(self::$unencryptedSizes[$normalizedPath]);
  		}
  
  		return $result;
  	}
  
  	/**
03e52840d   Kload   Init
190
191
192
193
194
195
  	 * @param string $path Path of file from which has been read
  	 * @param string $data Data that has been read from file
  	 */
  	public function postFile_get_contents($path, $data) {
  
  		$plainData = null;
6d9380f96   Cédric Dupont   Update sources OC...
196
  		$view = new \OC\Files\View('/');
03e52840d   Kload   Init
197

03e52840d   Kload   Init
198
199
200
201
202
203
204
205
  		// init session
  		$session = new \OCA\Encryption\Session($view);
  
  		// If data is a catfile
  		if (
  			Crypt::mode() === 'server'
  			&& Crypt::isCatfileContent($data)
  		) {
31b7f2792   Kload   Upgrade to ownclo...
206
  			$handle = fopen('crypt://' . $path, 'r');
03e52840d   Kload   Init
207
208
209
210
211
212
213
214
215
  
  			if (is_resource($handle)) {
  				while (($plainDataChunk = fgets($handle, 8192)) !== false) {
  					$plainData .= $plainDataChunk;
  				}
  			}
  
  		} elseif (
  			Crypt::mode() == 'server'
31b7f2792   Kload   Upgrade to ownclo...
216
  			&& \OC::$session->exists('legacyenckey')
03e52840d   Kload   Init
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
  			&& Crypt::isEncryptedMeta($path)
  		) {
  			// Disable encryption proxy to prevent recursive calls
  			$proxyStatus = \OC_FileProxy::$enabled;
  			\OC_FileProxy::$enabled = false;
  
  			$plainData = Crypt::legacyBlockDecrypt($data, $session->getLegacyKey());
  
  			\OC_FileProxy::$enabled = $proxyStatus;
  		}
  
  		if (!isset($plainData)) {
  
  			$plainData = $data;
  
  		}
  
  		return $plainData;
  
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
239
  	 * remember initial fopen mode because sometimes it gets changed during the request
a293d369c   Kload   Update sources to...
240
241
242
243
  	 * @param string $path path
  	 * @param string $mode type of access
  	 */
  	public function preFopen($path, $mode) {
6d9380f96   Cédric Dupont   Update sources OC...
244

a293d369c   Kload   Update sources to...
245
  		self::$fopenMode[$path] = $mode;
6d9380f96   Cédric Dupont   Update sources OC...
246
  		self::$enableEncryption = $this->shouldEncrypt($path, $mode);
a293d369c   Kload   Update sources to...
247
248
249
250
  	}
  
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
251
252
  	 * @param string $path
  	 * @param resource $result
03e52840d   Kload   Init
253
254
255
256
257
  	 * @return resource
  	 */
  	public function postFopen($path, &$result) {
  
  		$path = \OC\Files\Filesystem::normalizePath($path);
6d9380f96   Cédric Dupont   Update sources OC...
258
  		if (!$result || self::$enableEncryption === false) {
03e52840d   Kload   Init
259
260
261
262
  
  			return $result;
  
  		}
a293d369c   Kload   Update sources to...
263
  		// if we remember the mode from the pre proxy we re-use it
6d9380f96   Cédric Dupont   Update sources OC...
264
  		// otherwise we fall back to stream_get_meta_data()
a293d369c   Kload   Update sources to...
265
266
267
268
269
270
271
  		if (isset(self::$fopenMode[$path])) {
  			$mode = self::$fopenMode[$path];
  			unset(self::$fopenMode[$path]);
  		} else {
  			$meta = stream_get_meta_data($result);
  			$mode = $meta['mode'];
  		}
03e52840d   Kload   Init
272

6d9380f96   Cédric Dupont   Update sources OC...
273
274
  		// Close the original encrypted file
  		fclose($result);
03e52840d   Kload   Init
275

6d9380f96   Cédric Dupont   Update sources OC...
276
277
278
  		// Open the file using the crypto stream wrapper
  		// protocol and let it do the decryption work instead
  		$result = fopen('crypt://' . $path, $mode);
03e52840d   Kload   Init
279
280
281
282
283
284
  
  		return $result;
  
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
285
286
  	 * @param string $path
  	 * @param array $data
03e52840d   Kload   Init
287
288
289
290
291
  	 * @return array
  	 */
  	public function postGetFileInfo($path, $data) {
  
  		// if path is a folder do nothing
6d9380f96   Cédric Dupont   Update sources OC...
292
  		if (\OCP\App::isEnabled('files_encryption') && $data !== false && array_key_exists('size', $data)) {
03e52840d   Kload   Init
293
294
295
296
297
298
  
  			// Disable encryption proxy to prevent recursive calls
  			$proxyStatus = \OC_FileProxy::$enabled;
  			\OC_FileProxy::$enabled = false;
  
  			// get file size
6d9380f96   Cédric Dupont   Update sources OC...
299
  			$data['size'] = self::postFileSize($path, $data['size'], $data);
03e52840d   Kload   Init
300
301
302
303
304
305
306
307
308
  
  			// Re-enable the proxy
  			\OC_FileProxy::$enabled = $proxyStatus;
  		}
  
  		return $data;
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
309
310
311
  	 * @param string $path
  	 * @param int $size
  	 * @return int|bool
03e52840d   Kload   Init
312
  	 */
6d9380f96   Cédric Dupont   Update sources OC...
313
  	public function postFileSize($path, $size, $fileInfo = null) {
03e52840d   Kload   Init
314

6d9380f96   Cédric Dupont   Update sources OC...
315
  		$view = new \OC\Files\View('/');
03e52840d   Kload   Init
316

31b7f2792   Kload   Upgrade to ownclo...
317
  		$userId = Helper::getUser($path);
03e52840d   Kload   Init
318
319
320
321
322
323
324
325
326
327
328
  		$util = new Util($view, $userId);
  
  		// if encryption is no longer enabled or if the files aren't migrated yet
  		// we return the default file size
  		if(!\OCP\App::isEnabled('files_encryption') ||
  				$util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) {
  			return $size;
  		}
  
  		// if path is a folder do nothing
  		if ($view->is_dir($path)) {
837968727   Kload   [enh] Upgrade to ...
329
330
331
332
333
334
335
  			$proxyState = \OC_FileProxy::$enabled;
  			\OC_FileProxy::$enabled = false;
  			$fileInfo = $view->getFileInfo($path);
  			\OC_FileProxy::$enabled = $proxyState;
  			if (isset($fileInfo['unencrypted_size']) && $fileInfo['unencrypted_size'] > 0) {
  				return $fileInfo['unencrypted_size'];
  			}
03e52840d   Kload   Init
336
337
338
339
340
341
342
343
344
345
  			return $size;
  		}
  
  		// get relative path
  		$relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  
  		// if path is empty we cannot resolve anything
  		if (empty($relativePath)) {
  			return $size;
  		}
03e52840d   Kload   Init
346
  		// get file info from database/cache if not .part file
6d9380f96   Cédric Dupont   Update sources OC...
347
  		if (empty($fileInfo) && !Helper::isPartialFilePath($path)) {
31b7f2792   Kload   Upgrade to ownclo...
348
349
  			$proxyState = \OC_FileProxy::$enabled;
  			\OC_FileProxy::$enabled = false;
03e52840d   Kload   Init
350
  			$fileInfo = $view->getFileInfo($path);
31b7f2792   Kload   Upgrade to ownclo...
351
  			\OC_FileProxy::$enabled = $proxyState;
03e52840d   Kload   Init
352
353
354
  		}
  
  		// if file is encrypted return real file size
6d9380f96   Cédric Dupont   Update sources OC...
355
  		if (isset($fileInfo['encrypted']) && $fileInfo['encrypted'] === true) {
03e52840d   Kload   Init
356
  			// try to fix unencrypted file size if it doesn't look plausible
31b7f2792   Kload   Upgrade to ownclo...
357
  			if ((int)$fileInfo['size'] > 0 && (int)$fileInfo['unencrypted_size'] === 0 ) {
03e52840d   Kload   Init
358
359
360
  				$fixSize = $util->getFileSize($path);
  				$fileInfo['unencrypted_size'] = $fixSize;
  				// put file info if not .part file
31b7f2792   Kload   Upgrade to ownclo...
361
  				if (!Helper::isPartialFilePath($relativePath)) {
6d9380f96   Cédric Dupont   Update sources OC...
362
  					$view->putFileInfo($path, array('unencrypted_size' => $fixSize));
03e52840d   Kload   Init
363
364
365
366
  				}
  			}
  			$size = $fileInfo['unencrypted_size'];
  		} else {
6d9380f96   Cédric Dupont   Update sources OC...
367
368
  
  			$fileInfoUpdates = array();
03e52840d   Kload   Init
369
370
371
372
  
  			$fixSize = $util->getFileSize($path);
  			if ($fixSize > 0) {
  				$size = $fixSize;
6d9380f96   Cédric Dupont   Update sources OC...
373
374
  				$fileInfoUpdates['encrypted'] = true;
  				$fileInfoUpdates['unencrypted_size'] = $size;
03e52840d   Kload   Init
375
376
  
  				// put file info if not .part file
31b7f2792   Kload   Upgrade to ownclo...
377
  				if (!Helper::isPartialFilePath($relativePath)) {
6d9380f96   Cédric Dupont   Update sources OC...
378
  					$view->putFileInfo($path, $fileInfoUpdates);
03e52840d   Kload   Init
379
380
381
382
383
384
  				}
  			}
  
  		}
  		return $size;
  	}
03e52840d   Kload   Init
385
  }