Passed
Push — master ( 50faf9...001278 )
by Julius
32:17 queued 18:35
created
apps/encryption/lib/Crypto/Crypt.php 1 patch
Indentation   +697 added lines, -697 removed lines patch added patch discarded remove patch
@@ -56,701 +56,701 @@
 block discarded – undo
56 56
  * @package OCA\Encryption\Crypto
57 57
  */
58 58
 class Crypt {
59
-	public const SUPPORTED_CIPHERS_AND_KEY_SIZE = [
60
-		'AES-256-CTR' => 32,
61
-		'AES-128-CTR' => 16,
62
-		'AES-256-CFB' => 32,
63
-		'AES-128-CFB' => 16,
64
-	];
65
-	// one out of SUPPORTED_CIPHERS_AND_KEY_SIZE
66
-	public const DEFAULT_CIPHER = 'AES-256-CTR';
67
-	// default cipher from old Nextcloud versions
68
-	public const LEGACY_CIPHER = 'AES-128-CFB';
69
-
70
-	public const SUPPORTED_KEY_FORMATS = ['hash', 'password'];
71
-	// one out of SUPPORTED_KEY_FORMATS
72
-	public const DEFAULT_KEY_FORMAT = 'hash';
73
-	// default key format, old Nextcloud version encrypted the private key directly
74
-	// with the user password
75
-	public const LEGACY_KEY_FORMAT = 'password';
76
-
77
-	public const HEADER_START = 'HBEGIN';
78
-	public const HEADER_END = 'HEND';
79
-
80
-	// default encoding format, old Nextcloud versions used base64
81
-	public const BINARY_ENCODING_FORMAT = 'binary';
82
-
83
-	/** @var ILogger */
84
-	private $logger;
85
-
86
-	/** @var string */
87
-	private $user;
88
-
89
-	/** @var IConfig */
90
-	private $config;
91
-
92
-	/** @var IL10N */
93
-	private $l;
94
-
95
-	/** @var string|null */
96
-	private $currentCipher;
97
-
98
-	/** @var bool */
99
-	private $supportLegacy;
100
-
101
-	/**
102
-	 * Use the legacy base64 encoding instead of the more space-efficient binary encoding.
103
-	 */
104
-	private bool $useLegacyBase64Encoding;
105
-
106
-	/**
107
-	 * @param ILogger $logger
108
-	 * @param IUserSession $userSession
109
-	 * @param IConfig $config
110
-	 * @param IL10N $l
111
-	 */
112
-	public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
113
-		$this->logger = $logger;
114
-		$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
115
-		$this->config = $config;
116
-		$this->l = $l;
117
-		$this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
118
-		$this->useLegacyBase64Encoding = $this->config->getSystemValueBool('encryption.use_legacy_base64_encoding', false);
119
-	}
120
-
121
-	/**
122
-	 * create new private/public key-pair for user
123
-	 *
124
-	 * @return array|bool
125
-	 */
126
-	public function createKeyPair() {
127
-		$log = $this->logger;
128
-		$res = $this->getOpenSSLPKey();
129
-
130
-		if (!$res) {
131
-			$log->error("Encryption Library couldn't generate users key-pair for {$this->user}",
132
-				['app' => 'encryption']);
133
-
134
-			if (openssl_error_string()) {
135
-				$log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
136
-					['app' => 'encryption']);
137
-			}
138
-		} elseif (openssl_pkey_export($res,
139
-			$privateKey,
140
-			null,
141
-			$this->getOpenSSLConfig())) {
142
-			$keyDetails = openssl_pkey_get_details($res);
143
-			$publicKey = $keyDetails['key'];
144
-
145
-			return [
146
-				'publicKey' => $publicKey,
147
-				'privateKey' => $privateKey
148
-			];
149
-		}
150
-		$log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user,
151
-			['app' => 'encryption']);
152
-		if (openssl_error_string()) {
153
-			$log->error('Encryption Library:' . openssl_error_string(),
154
-				['app' => 'encryption']);
155
-		}
156
-
157
-		return false;
158
-	}
159
-
160
-	/**
161
-	 * Generates a new private key
162
-	 *
163
-	 * @return resource
164
-	 */
165
-	public function getOpenSSLPKey() {
166
-		$config = $this->getOpenSSLConfig();
167
-		return openssl_pkey_new($config);
168
-	}
169
-
170
-	/**
171
-	 * get openSSL Config
172
-	 *
173
-	 * @return array
174
-	 */
175
-	private function getOpenSSLConfig() {
176
-		$config = ['private_key_bits' => 4096];
177
-		$config = array_merge(
178
-			$config,
179
-			$this->config->getSystemValue('openssl', [])
180
-		);
181
-		return $config;
182
-	}
183
-
184
-	/**
185
-	 * @param string $plainContent
186
-	 * @param string $passPhrase
187
-	 * @param int $version
188
-	 * @param int $position
189
-	 * @return false|string
190
-	 * @throws EncryptionFailedException
191
-	 */
192
-	public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
193
-		if (!$plainContent) {
194
-			$this->logger->error('Encryption Library, symmetrical encryption failed no content given',
195
-				['app' => 'encryption']);
196
-			return false;
197
-		}
198
-
199
-		$iv = $this->generateIv();
200
-
201
-		$encryptedContent = $this->encrypt($plainContent,
202
-			$iv,
203
-			$passPhrase,
204
-			$this->getCipher());
205
-
206
-		// Create a signature based on the key as well as the current version
207
-		$sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position);
208
-
209
-		// combine content to encrypt the IV identifier and actual IV
210
-		$catFile = $this->concatIV($encryptedContent, $iv);
211
-		$catFile = $this->concatSig($catFile, $sig);
212
-		return $this->addPadding($catFile);
213
-	}
214
-
215
-	/**
216
-	 * generate header for encrypted file
217
-	 *
218
-	 * @param string $keyFormat see SUPPORTED_KEY_FORMATS
219
-	 * @return string
220
-	 * @throws \InvalidArgumentException
221
-	 */
222
-	public function generateHeader($keyFormat = self::DEFAULT_KEY_FORMAT) {
223
-		if (in_array($keyFormat, self::SUPPORTED_KEY_FORMATS, true) === false) {
224
-			throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
225
-		}
226
-
227
-		$header = self::HEADER_START
228
-			. ':cipher:' . $this->getCipher()
229
-			. ':keyFormat:' . $keyFormat;
230
-
231
-		if ($this->useLegacyBase64Encoding !== true) {
232
-			$header .= ':encoding:' . self::BINARY_ENCODING_FORMAT;
233
-		}
234
-
235
-		$header .= ':' . self::HEADER_END;
236
-
237
-		return $header;
238
-	}
239
-
240
-	/**
241
-	 * @param string $plainContent
242
-	 * @param string $iv
243
-	 * @param string $passPhrase
244
-	 * @param string $cipher
245
-	 * @return string
246
-	 * @throws EncryptionFailedException
247
-	 */
248
-	private function encrypt($plainContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
249
-		$options = $this->useLegacyBase64Encoding ? 0 : OPENSSL_RAW_DATA;
250
-		$encryptedContent = openssl_encrypt($plainContent,
251
-			$cipher,
252
-			$passPhrase,
253
-			$options,
254
-			$iv);
255
-
256
-		if (!$encryptedContent) {
257
-			$error = 'Encryption (symmetric) of content failed';
258
-			$this->logger->error($error . openssl_error_string(),
259
-				['app' => 'encryption']);
260
-			throw new EncryptionFailedException($error);
261
-		}
262
-
263
-		return $encryptedContent;
264
-	}
265
-
266
-	/**
267
-	 * return cipher either from config.php or the default cipher defined in
268
-	 * this class
269
-	 *
270
-	 * @return string
271
-	 */
272
-	private function getCachedCipher() {
273
-		if (isset($this->currentCipher)) {
274
-			return $this->currentCipher;
275
-		}
276
-
277
-		// Get cipher either from config.php or the default cipher defined in this class
278
-		$cipher = $this->config->getSystemValueString('cipher', self::DEFAULT_CIPHER);
279
-		if (!isset(self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher])) {
280
-			$this->logger->warning(
281
-				sprintf(
282
-					'Unsupported cipher (%s) defined in config.php supported. Falling back to %s',
283
-					$cipher,
284
-					self::DEFAULT_CIPHER
285
-				),
286
-				['app' => 'encryption']
287
-			);
288
-			$cipher = self::DEFAULT_CIPHER;
289
-		}
290
-
291
-		// Remember current cipher to avoid frequent lookups
292
-		$this->currentCipher = $cipher;
293
-		return $this->currentCipher;
294
-	}
295
-
296
-	/**
297
-	 * return current encryption cipher
298
-	 *
299
-	 * @return string
300
-	 */
301
-	public function getCipher() {
302
-		return $this->getCachedCipher();
303
-	}
304
-
305
-	/**
306
-	 * get key size depending on the cipher
307
-	 *
308
-	 * @param string $cipher
309
-	 * @return int
310
-	 * @throws \InvalidArgumentException
311
-	 */
312
-	protected function getKeySize($cipher) {
313
-		if (isset(self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher])) {
314
-			return self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher];
315
-		}
316
-
317
-		throw new \InvalidArgumentException(
318
-			sprintf(
319
-					'Unsupported cipher (%s) defined.',
320
-					$cipher
321
-			)
322
-		);
323
-	}
324
-
325
-	/**
326
-	 * get legacy cipher
327
-	 *
328
-	 * @return string
329
-	 */
330
-	public function getLegacyCipher() {
331
-		if (!$this->supportLegacy) {
332
-			throw new ServerNotAvailableException('Legacy cipher is no longer supported!');
333
-		}
334
-
335
-		return self::LEGACY_CIPHER;
336
-	}
337
-
338
-	/**
339
-	 * @param string $encryptedContent
340
-	 * @param string $iv
341
-	 * @return string
342
-	 */
343
-	private function concatIV($encryptedContent, $iv) {
344
-		return $encryptedContent . '00iv00' . $iv;
345
-	}
346
-
347
-	/**
348
-	 * @param string $encryptedContent
349
-	 * @param string $signature
350
-	 * @return string
351
-	 */
352
-	private function concatSig($encryptedContent, $signature) {
353
-		return $encryptedContent . '00sig00' . $signature;
354
-	}
355
-
356
-	/**
357
-	 * Note: This is _NOT_ a padding used for encryption purposes. It is solely
358
-	 * used to achieve the PHP stream size. It has _NOTHING_ to do with the
359
-	 * encrypted content and is not used in any crypto primitive.
360
-	 *
361
-	 * @param string $data
362
-	 * @return string
363
-	 */
364
-	private function addPadding($data) {
365
-		return $data . 'xxx';
366
-	}
367
-
368
-	/**
369
-	 * generate password hash used to encrypt the users private key
370
-	 *
371
-	 * @param string $password
372
-	 * @param string $cipher
373
-	 * @param string $uid only used for user keys
374
-	 * @return string
375
-	 */
376
-	protected function generatePasswordHash($password, $cipher, $uid = '') {
377
-		$instanceId = $this->config->getSystemValue('instanceid');
378
-		$instanceSecret = $this->config->getSystemValue('secret');
379
-		$salt = hash('sha256', $uid . $instanceId . $instanceSecret, true);
380
-		$keySize = $this->getKeySize($cipher);
381
-
382
-		$hash = hash_pbkdf2(
383
-			'sha256',
384
-			$password,
385
-			$salt,
386
-			100000,
387
-			$keySize,
388
-			true
389
-		);
390
-
391
-		return $hash;
392
-	}
393
-
394
-	/**
395
-	 * encrypt private key
396
-	 *
397
-	 * @param string $privateKey
398
-	 * @param string $password
399
-	 * @param string $uid for regular users, empty for system keys
400
-	 * @return false|string
401
-	 */
402
-	public function encryptPrivateKey($privateKey, $password, $uid = '') {
403
-		$cipher = $this->getCipher();
404
-		$hash = $this->generatePasswordHash($password, $cipher, $uid);
405
-		$encryptedKey = $this->symmetricEncryptFileContent(
406
-			$privateKey,
407
-			$hash,
408
-			0,
409
-			0
410
-		);
411
-
412
-		return $encryptedKey;
413
-	}
414
-
415
-	/**
416
-	 * @param string $privateKey
417
-	 * @param string $password
418
-	 * @param string $uid for regular users, empty for system keys
419
-	 * @return false|string
420
-	 */
421
-	public function decryptPrivateKey($privateKey, $password = '', $uid = '') {
422
-		$header = $this->parseHeader($privateKey);
423
-
424
-		if (isset($header['cipher'])) {
425
-			$cipher = $header['cipher'];
426
-		} else {
427
-			$cipher = $this->getLegacyCipher();
428
-		}
429
-
430
-		if (isset($header['keyFormat'])) {
431
-			$keyFormat = $header['keyFormat'];
432
-		} else {
433
-			$keyFormat = self::LEGACY_KEY_FORMAT;
434
-		}
435
-
436
-		if ($keyFormat === self::DEFAULT_KEY_FORMAT) {
437
-			$password = $this->generatePasswordHash($password, $cipher, $uid);
438
-		}
439
-
440
-		$binaryEncoding = isset($header['encoding']) && $header['encoding'] === self::BINARY_ENCODING_FORMAT;
441
-
442
-		// If we found a header we need to remove it from the key we want to decrypt
443
-		if (!empty($header)) {
444
-			$privateKey = substr($privateKey,
445
-				strpos($privateKey,
446
-					self::HEADER_END) + strlen(self::HEADER_END));
447
-		}
448
-
449
-		$plainKey = $this->symmetricDecryptFileContent(
450
-			$privateKey,
451
-			$password,
452
-			$cipher,
453
-			0,
454
-			0,
455
-			$binaryEncoding
456
-		);
457
-
458
-		if ($this->isValidPrivateKey($plainKey) === false) {
459
-			return false;
460
-		}
461
-
462
-		return $plainKey;
463
-	}
464
-
465
-	/**
466
-	 * check if it is a valid private key
467
-	 *
468
-	 * @param string $plainKey
469
-	 * @return bool
470
-	 */
471
-	protected function isValidPrivateKey($plainKey) {
472
-		$res = openssl_get_privatekey($plainKey);
473
-		// TODO: remove resource check one php7.4 is not longer supported
474
-		if (is_resource($res) || (is_object($res) && get_class($res) === 'OpenSSLAsymmetricKey')) {
475
-			$sslInfo = openssl_pkey_get_details($res);
476
-			if (isset($sslInfo['key'])) {
477
-				return true;
478
-			}
479
-		}
480
-
481
-		return false;
482
-	}
483
-
484
-	/**
485
-	 * @param string $keyFileContents
486
-	 * @param string $passPhrase
487
-	 * @param string $cipher
488
-	 * @param int $version
489
-	 * @param int|string $position
490
-	 * @param boolean $binaryEncoding
491
-	 * @return string
492
-	 * @throws DecryptionFailedException
493
-	 */
494
-	public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER, $version = 0, $position = 0, bool $binaryEncoding = false) {
495
-		if ($keyFileContents == '') {
496
-			return '';
497
-		}
498
-
499
-		$catFile = $this->splitMetaData($keyFileContents, $cipher);
500
-
501
-		if ($catFile['signature'] !== false) {
502
-			try {
503
-				// First try the new format
504
-				$this->checkSignature($catFile['encrypted'], $passPhrase . '_' . $version . '_' . $position, $catFile['signature']);
505
-			} catch (GenericEncryptionException $e) {
506
-				// For compatibility with old files check the version without _
507
-				$this->checkSignature($catFile['encrypted'], $passPhrase . $version . $position, $catFile['signature']);
508
-			}
509
-		}
510
-
511
-		return $this->decrypt($catFile['encrypted'],
512
-			$catFile['iv'],
513
-			$passPhrase,
514
-			$cipher,
515
-			$binaryEncoding);
516
-	}
517
-
518
-	/**
519
-	 * check for valid signature
520
-	 *
521
-	 * @param string $data
522
-	 * @param string $passPhrase
523
-	 * @param string $expectedSignature
524
-	 * @throws GenericEncryptionException
525
-	 */
526
-	private function checkSignature($data, $passPhrase, $expectedSignature) {
527
-		$enforceSignature = !$this->config->getSystemValueBool('encryption_skip_signature_check', false);
528
-
529
-		$signature = $this->createSignature($data, $passPhrase);
530
-		$isCorrectHash = hash_equals($expectedSignature, $signature);
531
-
532
-		if (!$isCorrectHash && $enforceSignature) {
533
-			throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
534
-		} elseif (!$isCorrectHash && !$enforceSignature) {
535
-			$this->logger->info("Signature check skipped", ['app' => 'encryption']);
536
-		}
537
-	}
538
-
539
-	/**
540
-	 * create signature
541
-	 *
542
-	 * @param string $data
543
-	 * @param string $passPhrase
544
-	 * @return string
545
-	 */
546
-	private function createSignature($data, $passPhrase) {
547
-		$passPhrase = hash('sha512', $passPhrase . 'a', true);
548
-		return hash_hmac('sha256', $data, $passPhrase);
549
-	}
550
-
551
-
552
-	/**
553
-	 * remove padding
554
-	 *
555
-	 * @param string $padded
556
-	 * @param bool $hasSignature did the block contain a signature, in this case we use a different padding
557
-	 * @return string|false
558
-	 */
559
-	private function removePadding($padded, $hasSignature = false) {
560
-		if ($hasSignature === false && substr($padded, -2) === 'xx') {
561
-			return substr($padded, 0, -2);
562
-		} elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
563
-			return substr($padded, 0, -3);
564
-		}
565
-		return false;
566
-	}
567
-
568
-	/**
569
-	 * split meta data from encrypted file
570
-	 * Note: for now, we assume that the meta data always start with the iv
571
-	 *       followed by the signature, if available
572
-	 *
573
-	 * @param string $catFile
574
-	 * @param string $cipher
575
-	 * @return array
576
-	 */
577
-	private function splitMetaData($catFile, $cipher) {
578
-		if ($this->hasSignature($catFile, $cipher)) {
579
-			$catFile = $this->removePadding($catFile, true);
580
-			$meta = substr($catFile, -93);
581
-			$iv = substr($meta, strlen('00iv00'), 16);
582
-			$sig = substr($meta, 22 + strlen('00sig00'));
583
-			$encrypted = substr($catFile, 0, -93);
584
-		} else {
585
-			$catFile = $this->removePadding($catFile);
586
-			$meta = substr($catFile, -22);
587
-			$iv = substr($meta, -16);
588
-			$sig = false;
589
-			$encrypted = substr($catFile, 0, -22);
590
-		}
591
-
592
-		return [
593
-			'encrypted' => $encrypted,
594
-			'iv' => $iv,
595
-			'signature' => $sig
596
-		];
597
-	}
598
-
599
-	/**
600
-	 * check if encrypted block is signed
601
-	 *
602
-	 * @param string $catFile
603
-	 * @param string $cipher
604
-	 * @return bool
605
-	 * @throws GenericEncryptionException
606
-	 */
607
-	private function hasSignature($catFile, $cipher) {
608
-		$skipSignatureCheck = $this->config->getSystemValueBool('encryption_skip_signature_check', false);
609
-
610
-		$meta = substr($catFile, -93);
611
-		$signaturePosition = strpos($meta, '00sig00');
612
-
613
-		// If we no longer support the legacy format then everything needs a signature
614
-		if (!$skipSignatureCheck && !$this->supportLegacy && $signaturePosition === false) {
615
-			throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
616
-		}
617
-
618
-		// Enforce signature for the new 'CTR' ciphers
619
-		if (!$skipSignatureCheck && $signaturePosition === false && stripos($cipher, 'ctr') !== false) {
620
-			throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
621
-		}
622
-
623
-		return ($signaturePosition !== false);
624
-	}
625
-
626
-
627
-	/**
628
-	 * @param string $encryptedContent
629
-	 * @param string $iv
630
-	 * @param string $passPhrase
631
-	 * @param string $cipher
632
-	 * @param boolean $binaryEncoding
633
-	 * @return string
634
-	 * @throws DecryptionFailedException
635
-	 */
636
-	private function decrypt(string $encryptedContent, string $iv, string $passPhrase = '', string $cipher = self::DEFAULT_CIPHER, bool $binaryEncoding = false): string {
637
-		$options = $binaryEncoding === true ? OPENSSL_RAW_DATA : 0;
638
-		$plainContent = openssl_decrypt($encryptedContent,
639
-			$cipher,
640
-			$passPhrase,
641
-			$options,
642
-			$iv);
643
-
644
-		if ($plainContent) {
645
-			return $plainContent;
646
-		} else {
647
-			throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
648
-		}
649
-	}
650
-
651
-	/**
652
-	 * @param string $data
653
-	 * @return array
654
-	 */
655
-	protected function parseHeader($data) {
656
-		$result = [];
657
-
658
-		if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
659
-			$endAt = strpos($data, self::HEADER_END);
660
-			$header = substr($data, 0, $endAt + strlen(self::HEADER_END));
661
-
662
-			// +1 not to start with an ':' which would result in empty element at the beginning
663
-			$exploded = explode(':',
664
-				substr($header, strlen(self::HEADER_START) + 1));
665
-
666
-			$element = array_shift($exploded);
667
-
668
-			while ($element !== self::HEADER_END) {
669
-				$result[$element] = array_shift($exploded);
670
-				$element = array_shift($exploded);
671
-			}
672
-		}
673
-
674
-		return $result;
675
-	}
676
-
677
-	/**
678
-	 * generate initialization vector
679
-	 *
680
-	 * @return string
681
-	 * @throws GenericEncryptionException
682
-	 */
683
-	private function generateIv() {
684
-		return random_bytes(16);
685
-	}
686
-
687
-	/**
688
-	 * Generate a cryptographically secure pseudo-random 256-bit ASCII key, used
689
-	 * as file key
690
-	 *
691
-	 * @return string
692
-	 * @throws \Exception
693
-	 */
694
-	public function generateFileKey() {
695
-		return random_bytes(32);
696
-	}
697
-
698
-	/**
699
-	 * @param $encKeyFile
700
-	 * @param $shareKey
701
-	 * @param $privateKey
702
-	 * @return string
703
-	 * @throws MultiKeyDecryptException
704
-	 */
705
-	public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
706
-		if (!$encKeyFile) {
707
-			throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
708
-		}
709
-
710
-		if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
711
-			return $plainContent;
712
-		} else {
713
-			throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
714
-		}
715
-	}
716
-
717
-	/**
718
-	 * @param string $plainContent
719
-	 * @param array $keyFiles
720
-	 * @return array
721
-	 * @throws MultiKeyEncryptException
722
-	 */
723
-	public function multiKeyEncrypt($plainContent, array $keyFiles) {
724
-		// openssl_seal returns false without errors if plaincontent is empty
725
-		// so trigger our own error
726
-		if (empty($plainContent)) {
727
-			throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
728
-		}
729
-
730
-		// Set empty vars to be set by openssl by reference
731
-		$sealed = '';
732
-		$shareKeys = [];
733
-		$mappedShareKeys = [];
734
-
735
-		if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
736
-			$i = 0;
737
-
738
-			// Ensure each shareKey is labelled with its corresponding key id
739
-			foreach ($keyFiles as $userId => $publicKey) {
740
-				$mappedShareKeys[$userId] = $shareKeys[$i];
741
-				$i++;
742
-			}
743
-
744
-			return [
745
-				'keys' => $mappedShareKeys,
746
-				'data' => $sealed
747
-			];
748
-		} else {
749
-			throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
750
-		}
751
-	}
752
-
753
-	public function useLegacyBase64Encoding(): bool {
754
-		return $this->useLegacyBase64Encoding;
755
-	}
59
+    public const SUPPORTED_CIPHERS_AND_KEY_SIZE = [
60
+        'AES-256-CTR' => 32,
61
+        'AES-128-CTR' => 16,
62
+        'AES-256-CFB' => 32,
63
+        'AES-128-CFB' => 16,
64
+    ];
65
+    // one out of SUPPORTED_CIPHERS_AND_KEY_SIZE
66
+    public const DEFAULT_CIPHER = 'AES-256-CTR';
67
+    // default cipher from old Nextcloud versions
68
+    public const LEGACY_CIPHER = 'AES-128-CFB';
69
+
70
+    public const SUPPORTED_KEY_FORMATS = ['hash', 'password'];
71
+    // one out of SUPPORTED_KEY_FORMATS
72
+    public const DEFAULT_KEY_FORMAT = 'hash';
73
+    // default key format, old Nextcloud version encrypted the private key directly
74
+    // with the user password
75
+    public const LEGACY_KEY_FORMAT = 'password';
76
+
77
+    public const HEADER_START = 'HBEGIN';
78
+    public const HEADER_END = 'HEND';
79
+
80
+    // default encoding format, old Nextcloud versions used base64
81
+    public const BINARY_ENCODING_FORMAT = 'binary';
82
+
83
+    /** @var ILogger */
84
+    private $logger;
85
+
86
+    /** @var string */
87
+    private $user;
88
+
89
+    /** @var IConfig */
90
+    private $config;
91
+
92
+    /** @var IL10N */
93
+    private $l;
94
+
95
+    /** @var string|null */
96
+    private $currentCipher;
97
+
98
+    /** @var bool */
99
+    private $supportLegacy;
100
+
101
+    /**
102
+     * Use the legacy base64 encoding instead of the more space-efficient binary encoding.
103
+     */
104
+    private bool $useLegacyBase64Encoding;
105
+
106
+    /**
107
+     * @param ILogger $logger
108
+     * @param IUserSession $userSession
109
+     * @param IConfig $config
110
+     * @param IL10N $l
111
+     */
112
+    public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
113
+        $this->logger = $logger;
114
+        $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
115
+        $this->config = $config;
116
+        $this->l = $l;
117
+        $this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
118
+        $this->useLegacyBase64Encoding = $this->config->getSystemValueBool('encryption.use_legacy_base64_encoding', false);
119
+    }
120
+
121
+    /**
122
+     * create new private/public key-pair for user
123
+     *
124
+     * @return array|bool
125
+     */
126
+    public function createKeyPair() {
127
+        $log = $this->logger;
128
+        $res = $this->getOpenSSLPKey();
129
+
130
+        if (!$res) {
131
+            $log->error("Encryption Library couldn't generate users key-pair for {$this->user}",
132
+                ['app' => 'encryption']);
133
+
134
+            if (openssl_error_string()) {
135
+                $log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
136
+                    ['app' => 'encryption']);
137
+            }
138
+        } elseif (openssl_pkey_export($res,
139
+            $privateKey,
140
+            null,
141
+            $this->getOpenSSLConfig())) {
142
+            $keyDetails = openssl_pkey_get_details($res);
143
+            $publicKey = $keyDetails['key'];
144
+
145
+            return [
146
+                'publicKey' => $publicKey,
147
+                'privateKey' => $privateKey
148
+            ];
149
+        }
150
+        $log->error('Encryption library couldn\'t export users private key, please check your servers OpenSSL configuration.' . $this->user,
151
+            ['app' => 'encryption']);
152
+        if (openssl_error_string()) {
153
+            $log->error('Encryption Library:' . openssl_error_string(),
154
+                ['app' => 'encryption']);
155
+        }
156
+
157
+        return false;
158
+    }
159
+
160
+    /**
161
+     * Generates a new private key
162
+     *
163
+     * @return resource
164
+     */
165
+    public function getOpenSSLPKey() {
166
+        $config = $this->getOpenSSLConfig();
167
+        return openssl_pkey_new($config);
168
+    }
169
+
170
+    /**
171
+     * get openSSL Config
172
+     *
173
+     * @return array
174
+     */
175
+    private function getOpenSSLConfig() {
176
+        $config = ['private_key_bits' => 4096];
177
+        $config = array_merge(
178
+            $config,
179
+            $this->config->getSystemValue('openssl', [])
180
+        );
181
+        return $config;
182
+    }
183
+
184
+    /**
185
+     * @param string $plainContent
186
+     * @param string $passPhrase
187
+     * @param int $version
188
+     * @param int $position
189
+     * @return false|string
190
+     * @throws EncryptionFailedException
191
+     */
192
+    public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
193
+        if (!$plainContent) {
194
+            $this->logger->error('Encryption Library, symmetrical encryption failed no content given',
195
+                ['app' => 'encryption']);
196
+            return false;
197
+        }
198
+
199
+        $iv = $this->generateIv();
200
+
201
+        $encryptedContent = $this->encrypt($plainContent,
202
+            $iv,
203
+            $passPhrase,
204
+            $this->getCipher());
205
+
206
+        // Create a signature based on the key as well as the current version
207
+        $sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position);
208
+
209
+        // combine content to encrypt the IV identifier and actual IV
210
+        $catFile = $this->concatIV($encryptedContent, $iv);
211
+        $catFile = $this->concatSig($catFile, $sig);
212
+        return $this->addPadding($catFile);
213
+    }
214
+
215
+    /**
216
+     * generate header for encrypted file
217
+     *
218
+     * @param string $keyFormat see SUPPORTED_KEY_FORMATS
219
+     * @return string
220
+     * @throws \InvalidArgumentException
221
+     */
222
+    public function generateHeader($keyFormat = self::DEFAULT_KEY_FORMAT) {
223
+        if (in_array($keyFormat, self::SUPPORTED_KEY_FORMATS, true) === false) {
224
+            throw new \InvalidArgumentException('key format "' . $keyFormat . '" is not supported');
225
+        }
226
+
227
+        $header = self::HEADER_START
228
+            . ':cipher:' . $this->getCipher()
229
+            . ':keyFormat:' . $keyFormat;
230
+
231
+        if ($this->useLegacyBase64Encoding !== true) {
232
+            $header .= ':encoding:' . self::BINARY_ENCODING_FORMAT;
233
+        }
234
+
235
+        $header .= ':' . self::HEADER_END;
236
+
237
+        return $header;
238
+    }
239
+
240
+    /**
241
+     * @param string $plainContent
242
+     * @param string $iv
243
+     * @param string $passPhrase
244
+     * @param string $cipher
245
+     * @return string
246
+     * @throws EncryptionFailedException
247
+     */
248
+    private function encrypt($plainContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) {
249
+        $options = $this->useLegacyBase64Encoding ? 0 : OPENSSL_RAW_DATA;
250
+        $encryptedContent = openssl_encrypt($plainContent,
251
+            $cipher,
252
+            $passPhrase,
253
+            $options,
254
+            $iv);
255
+
256
+        if (!$encryptedContent) {
257
+            $error = 'Encryption (symmetric) of content failed';
258
+            $this->logger->error($error . openssl_error_string(),
259
+                ['app' => 'encryption']);
260
+            throw new EncryptionFailedException($error);
261
+        }
262
+
263
+        return $encryptedContent;
264
+    }
265
+
266
+    /**
267
+     * return cipher either from config.php or the default cipher defined in
268
+     * this class
269
+     *
270
+     * @return string
271
+     */
272
+    private function getCachedCipher() {
273
+        if (isset($this->currentCipher)) {
274
+            return $this->currentCipher;
275
+        }
276
+
277
+        // Get cipher either from config.php or the default cipher defined in this class
278
+        $cipher = $this->config->getSystemValueString('cipher', self::DEFAULT_CIPHER);
279
+        if (!isset(self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher])) {
280
+            $this->logger->warning(
281
+                sprintf(
282
+                    'Unsupported cipher (%s) defined in config.php supported. Falling back to %s',
283
+                    $cipher,
284
+                    self::DEFAULT_CIPHER
285
+                ),
286
+                ['app' => 'encryption']
287
+            );
288
+            $cipher = self::DEFAULT_CIPHER;
289
+        }
290
+
291
+        // Remember current cipher to avoid frequent lookups
292
+        $this->currentCipher = $cipher;
293
+        return $this->currentCipher;
294
+    }
295
+
296
+    /**
297
+     * return current encryption cipher
298
+     *
299
+     * @return string
300
+     */
301
+    public function getCipher() {
302
+        return $this->getCachedCipher();
303
+    }
304
+
305
+    /**
306
+     * get key size depending on the cipher
307
+     *
308
+     * @param string $cipher
309
+     * @return int
310
+     * @throws \InvalidArgumentException
311
+     */
312
+    protected function getKeySize($cipher) {
313
+        if (isset(self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher])) {
314
+            return self::SUPPORTED_CIPHERS_AND_KEY_SIZE[$cipher];
315
+        }
316
+
317
+        throw new \InvalidArgumentException(
318
+            sprintf(
319
+                    'Unsupported cipher (%s) defined.',
320
+                    $cipher
321
+            )
322
+        );
323
+    }
324
+
325
+    /**
326
+     * get legacy cipher
327
+     *
328
+     * @return string
329
+     */
330
+    public function getLegacyCipher() {
331
+        if (!$this->supportLegacy) {
332
+            throw new ServerNotAvailableException('Legacy cipher is no longer supported!');
333
+        }
334
+
335
+        return self::LEGACY_CIPHER;
336
+    }
337
+
338
+    /**
339
+     * @param string $encryptedContent
340
+     * @param string $iv
341
+     * @return string
342
+     */
343
+    private function concatIV($encryptedContent, $iv) {
344
+        return $encryptedContent . '00iv00' . $iv;
345
+    }
346
+
347
+    /**
348
+     * @param string $encryptedContent
349
+     * @param string $signature
350
+     * @return string
351
+     */
352
+    private function concatSig($encryptedContent, $signature) {
353
+        return $encryptedContent . '00sig00' . $signature;
354
+    }
355
+
356
+    /**
357
+     * Note: This is _NOT_ a padding used for encryption purposes. It is solely
358
+     * used to achieve the PHP stream size. It has _NOTHING_ to do with the
359
+     * encrypted content and is not used in any crypto primitive.
360
+     *
361
+     * @param string $data
362
+     * @return string
363
+     */
364
+    private function addPadding($data) {
365
+        return $data . 'xxx';
366
+    }
367
+
368
+    /**
369
+     * generate password hash used to encrypt the users private key
370
+     *
371
+     * @param string $password
372
+     * @param string $cipher
373
+     * @param string $uid only used for user keys
374
+     * @return string
375
+     */
376
+    protected function generatePasswordHash($password, $cipher, $uid = '') {
377
+        $instanceId = $this->config->getSystemValue('instanceid');
378
+        $instanceSecret = $this->config->getSystemValue('secret');
379
+        $salt = hash('sha256', $uid . $instanceId . $instanceSecret, true);
380
+        $keySize = $this->getKeySize($cipher);
381
+
382
+        $hash = hash_pbkdf2(
383
+            'sha256',
384
+            $password,
385
+            $salt,
386
+            100000,
387
+            $keySize,
388
+            true
389
+        );
390
+
391
+        return $hash;
392
+    }
393
+
394
+    /**
395
+     * encrypt private key
396
+     *
397
+     * @param string $privateKey
398
+     * @param string $password
399
+     * @param string $uid for regular users, empty for system keys
400
+     * @return false|string
401
+     */
402
+    public function encryptPrivateKey($privateKey, $password, $uid = '') {
403
+        $cipher = $this->getCipher();
404
+        $hash = $this->generatePasswordHash($password, $cipher, $uid);
405
+        $encryptedKey = $this->symmetricEncryptFileContent(
406
+            $privateKey,
407
+            $hash,
408
+            0,
409
+            0
410
+        );
411
+
412
+        return $encryptedKey;
413
+    }
414
+
415
+    /**
416
+     * @param string $privateKey
417
+     * @param string $password
418
+     * @param string $uid for regular users, empty for system keys
419
+     * @return false|string
420
+     */
421
+    public function decryptPrivateKey($privateKey, $password = '', $uid = '') {
422
+        $header = $this->parseHeader($privateKey);
423
+
424
+        if (isset($header['cipher'])) {
425
+            $cipher = $header['cipher'];
426
+        } else {
427
+            $cipher = $this->getLegacyCipher();
428
+        }
429
+
430
+        if (isset($header['keyFormat'])) {
431
+            $keyFormat = $header['keyFormat'];
432
+        } else {
433
+            $keyFormat = self::LEGACY_KEY_FORMAT;
434
+        }
435
+
436
+        if ($keyFormat === self::DEFAULT_KEY_FORMAT) {
437
+            $password = $this->generatePasswordHash($password, $cipher, $uid);
438
+        }
439
+
440
+        $binaryEncoding = isset($header['encoding']) && $header['encoding'] === self::BINARY_ENCODING_FORMAT;
441
+
442
+        // If we found a header we need to remove it from the key we want to decrypt
443
+        if (!empty($header)) {
444
+            $privateKey = substr($privateKey,
445
+                strpos($privateKey,
446
+                    self::HEADER_END) + strlen(self::HEADER_END));
447
+        }
448
+
449
+        $plainKey = $this->symmetricDecryptFileContent(
450
+            $privateKey,
451
+            $password,
452
+            $cipher,
453
+            0,
454
+            0,
455
+            $binaryEncoding
456
+        );
457
+
458
+        if ($this->isValidPrivateKey($plainKey) === false) {
459
+            return false;
460
+        }
461
+
462
+        return $plainKey;
463
+    }
464
+
465
+    /**
466
+     * check if it is a valid private key
467
+     *
468
+     * @param string $plainKey
469
+     * @return bool
470
+     */
471
+    protected function isValidPrivateKey($plainKey) {
472
+        $res = openssl_get_privatekey($plainKey);
473
+        // TODO: remove resource check one php7.4 is not longer supported
474
+        if (is_resource($res) || (is_object($res) && get_class($res) === 'OpenSSLAsymmetricKey')) {
475
+            $sslInfo = openssl_pkey_get_details($res);
476
+            if (isset($sslInfo['key'])) {
477
+                return true;
478
+            }
479
+        }
480
+
481
+        return false;
482
+    }
483
+
484
+    /**
485
+     * @param string $keyFileContents
486
+     * @param string $passPhrase
487
+     * @param string $cipher
488
+     * @param int $version
489
+     * @param int|string $position
490
+     * @param boolean $binaryEncoding
491
+     * @return string
492
+     * @throws DecryptionFailedException
493
+     */
494
+    public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER, $version = 0, $position = 0, bool $binaryEncoding = false) {
495
+        if ($keyFileContents == '') {
496
+            return '';
497
+        }
498
+
499
+        $catFile = $this->splitMetaData($keyFileContents, $cipher);
500
+
501
+        if ($catFile['signature'] !== false) {
502
+            try {
503
+                // First try the new format
504
+                $this->checkSignature($catFile['encrypted'], $passPhrase . '_' . $version . '_' . $position, $catFile['signature']);
505
+            } catch (GenericEncryptionException $e) {
506
+                // For compatibility with old files check the version without _
507
+                $this->checkSignature($catFile['encrypted'], $passPhrase . $version . $position, $catFile['signature']);
508
+            }
509
+        }
510
+
511
+        return $this->decrypt($catFile['encrypted'],
512
+            $catFile['iv'],
513
+            $passPhrase,
514
+            $cipher,
515
+            $binaryEncoding);
516
+    }
517
+
518
+    /**
519
+     * check for valid signature
520
+     *
521
+     * @param string $data
522
+     * @param string $passPhrase
523
+     * @param string $expectedSignature
524
+     * @throws GenericEncryptionException
525
+     */
526
+    private function checkSignature($data, $passPhrase, $expectedSignature) {
527
+        $enforceSignature = !$this->config->getSystemValueBool('encryption_skip_signature_check', false);
528
+
529
+        $signature = $this->createSignature($data, $passPhrase);
530
+        $isCorrectHash = hash_equals($expectedSignature, $signature);
531
+
532
+        if (!$isCorrectHash && $enforceSignature) {
533
+            throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature'));
534
+        } elseif (!$isCorrectHash && !$enforceSignature) {
535
+            $this->logger->info("Signature check skipped", ['app' => 'encryption']);
536
+        }
537
+    }
538
+
539
+    /**
540
+     * create signature
541
+     *
542
+     * @param string $data
543
+     * @param string $passPhrase
544
+     * @return string
545
+     */
546
+    private function createSignature($data, $passPhrase) {
547
+        $passPhrase = hash('sha512', $passPhrase . 'a', true);
548
+        return hash_hmac('sha256', $data, $passPhrase);
549
+    }
550
+
551
+
552
+    /**
553
+     * remove padding
554
+     *
555
+     * @param string $padded
556
+     * @param bool $hasSignature did the block contain a signature, in this case we use a different padding
557
+     * @return string|false
558
+     */
559
+    private function removePadding($padded, $hasSignature = false) {
560
+        if ($hasSignature === false && substr($padded, -2) === 'xx') {
561
+            return substr($padded, 0, -2);
562
+        } elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
563
+            return substr($padded, 0, -3);
564
+        }
565
+        return false;
566
+    }
567
+
568
+    /**
569
+     * split meta data from encrypted file
570
+     * Note: for now, we assume that the meta data always start with the iv
571
+     *       followed by the signature, if available
572
+     *
573
+     * @param string $catFile
574
+     * @param string $cipher
575
+     * @return array
576
+     */
577
+    private function splitMetaData($catFile, $cipher) {
578
+        if ($this->hasSignature($catFile, $cipher)) {
579
+            $catFile = $this->removePadding($catFile, true);
580
+            $meta = substr($catFile, -93);
581
+            $iv = substr($meta, strlen('00iv00'), 16);
582
+            $sig = substr($meta, 22 + strlen('00sig00'));
583
+            $encrypted = substr($catFile, 0, -93);
584
+        } else {
585
+            $catFile = $this->removePadding($catFile);
586
+            $meta = substr($catFile, -22);
587
+            $iv = substr($meta, -16);
588
+            $sig = false;
589
+            $encrypted = substr($catFile, 0, -22);
590
+        }
591
+
592
+        return [
593
+            'encrypted' => $encrypted,
594
+            'iv' => $iv,
595
+            'signature' => $sig
596
+        ];
597
+    }
598
+
599
+    /**
600
+     * check if encrypted block is signed
601
+     *
602
+     * @param string $catFile
603
+     * @param string $cipher
604
+     * @return bool
605
+     * @throws GenericEncryptionException
606
+     */
607
+    private function hasSignature($catFile, $cipher) {
608
+        $skipSignatureCheck = $this->config->getSystemValueBool('encryption_skip_signature_check', false);
609
+
610
+        $meta = substr($catFile, -93);
611
+        $signaturePosition = strpos($meta, '00sig00');
612
+
613
+        // If we no longer support the legacy format then everything needs a signature
614
+        if (!$skipSignatureCheck && !$this->supportLegacy && $signaturePosition === false) {
615
+            throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
616
+        }
617
+
618
+        // Enforce signature for the new 'CTR' ciphers
619
+        if (!$skipSignatureCheck && $signaturePosition === false && stripos($cipher, 'ctr') !== false) {
620
+            throw new GenericEncryptionException('Missing Signature', $this->l->t('Missing Signature'));
621
+        }
622
+
623
+        return ($signaturePosition !== false);
624
+    }
625
+
626
+
627
+    /**
628
+     * @param string $encryptedContent
629
+     * @param string $iv
630
+     * @param string $passPhrase
631
+     * @param string $cipher
632
+     * @param boolean $binaryEncoding
633
+     * @return string
634
+     * @throws DecryptionFailedException
635
+     */
636
+    private function decrypt(string $encryptedContent, string $iv, string $passPhrase = '', string $cipher = self::DEFAULT_CIPHER, bool $binaryEncoding = false): string {
637
+        $options = $binaryEncoding === true ? OPENSSL_RAW_DATA : 0;
638
+        $plainContent = openssl_decrypt($encryptedContent,
639
+            $cipher,
640
+            $passPhrase,
641
+            $options,
642
+            $iv);
643
+
644
+        if ($plainContent) {
645
+            return $plainContent;
646
+        } else {
647
+            throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
648
+        }
649
+    }
650
+
651
+    /**
652
+     * @param string $data
653
+     * @return array
654
+     */
655
+    protected function parseHeader($data) {
656
+        $result = [];
657
+
658
+        if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
659
+            $endAt = strpos($data, self::HEADER_END);
660
+            $header = substr($data, 0, $endAt + strlen(self::HEADER_END));
661
+
662
+            // +1 not to start with an ':' which would result in empty element at the beginning
663
+            $exploded = explode(':',
664
+                substr($header, strlen(self::HEADER_START) + 1));
665
+
666
+            $element = array_shift($exploded);
667
+
668
+            while ($element !== self::HEADER_END) {
669
+                $result[$element] = array_shift($exploded);
670
+                $element = array_shift($exploded);
671
+            }
672
+        }
673
+
674
+        return $result;
675
+    }
676
+
677
+    /**
678
+     * generate initialization vector
679
+     *
680
+     * @return string
681
+     * @throws GenericEncryptionException
682
+     */
683
+    private function generateIv() {
684
+        return random_bytes(16);
685
+    }
686
+
687
+    /**
688
+     * Generate a cryptographically secure pseudo-random 256-bit ASCII key, used
689
+     * as file key
690
+     *
691
+     * @return string
692
+     * @throws \Exception
693
+     */
694
+    public function generateFileKey() {
695
+        return random_bytes(32);
696
+    }
697
+
698
+    /**
699
+     * @param $encKeyFile
700
+     * @param $shareKey
701
+     * @param $privateKey
702
+     * @return string
703
+     * @throws MultiKeyDecryptException
704
+     */
705
+    public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
706
+        if (!$encKeyFile) {
707
+            throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
708
+        }
709
+
710
+        if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey, 'RC4')) {
711
+            return $plainContent;
712
+        } else {
713
+            throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string());
714
+        }
715
+    }
716
+
717
+    /**
718
+     * @param string $plainContent
719
+     * @param array $keyFiles
720
+     * @return array
721
+     * @throws MultiKeyEncryptException
722
+     */
723
+    public function multiKeyEncrypt($plainContent, array $keyFiles) {
724
+        // openssl_seal returns false without errors if plaincontent is empty
725
+        // so trigger our own error
726
+        if (empty($plainContent)) {
727
+            throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
728
+        }
729
+
730
+        // Set empty vars to be set by openssl by reference
731
+        $sealed = '';
732
+        $shareKeys = [];
733
+        $mappedShareKeys = [];
734
+
735
+        if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles, 'RC4')) {
736
+            $i = 0;
737
+
738
+            // Ensure each shareKey is labelled with its corresponding key id
739
+            foreach ($keyFiles as $userId => $publicKey) {
740
+                $mappedShareKeys[$userId] = $shareKeys[$i];
741
+                $i++;
742
+            }
743
+
744
+            return [
745
+                'keys' => $mappedShareKeys,
746
+                'data' => $sealed
747
+            ];
748
+        } else {
749
+            throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
750
+        }
751
+    }
752
+
753
+    public function useLegacyBase64Encoding(): bool {
754
+        return $this->useLegacyBase64Encoding;
755
+    }
756 756
 }
Please login to merge, or discard this patch.
apps/encryption/lib/Command/FixEncryptedVersion.php 1 patch
Indentation   +282 added lines, -282 removed lines patch added patch discarded remove patch
@@ -36,286 +36,286 @@
 block discarded – undo
36 36
 use Symfony\Component\Console\Output\OutputInterface;
37 37
 
38 38
 class FixEncryptedVersion extends Command {
39
-	/** @var IConfig */
40
-	private $config;
41
-
42
-	/** @var ILogger */
43
-	private $logger;
44
-
45
-	/** @var IRootFolder  */
46
-	private $rootFolder;
47
-
48
-	/** @var IUserManager  */
49
-	private $userManager;
50
-
51
-	/** @var Util */
52
-	private $util;
53
-
54
-	/** @var View  */
55
-	private $view;
56
-
57
-	/** @var bool */
58
-	private $supportLegacy;
59
-
60
-	public function __construct(
61
-		IConfig $config,
62
-		ILogger $logger,
63
-		IRootFolder $rootFolder,
64
-		IUserManager $userManager,
65
-		Util $util,
66
-		View $view
67
-	) {
68
-		$this->config = $config;
69
-		$this->logger = $logger;
70
-		$this->rootFolder = $rootFolder;
71
-		$this->userManager = $userManager;
72
-		$this->util = $util;
73
-		$this->view = $view;
74
-		$this->supportLegacy = false;
75
-
76
-		parent::__construct();
77
-	}
78
-
79
-	protected function configure(): void {
80
-		parent::configure();
81
-
82
-		$this
83
-			->setName('encryption:fix-encrypted-version')
84
-			->setDescription('Fix the encrypted version if the encrypted file(s) are not downloadable.')
85
-			->addArgument(
86
-				'user',
87
-				InputArgument::REQUIRED,
88
-				'The id of the user whose files need fixing'
89
-			)->addOption(
90
-				'path',
91
-				'p',
92
-				InputArgument::OPTIONAL,
93
-				'Limit files to fix with path, e.g., --path="/Music/Artist". If path indicates a directory, all the files inside directory will be fixed.'
94
-			);
95
-	}
96
-
97
-	protected function execute(InputInterface $input, OutputInterface $output): int {
98
-		$skipSignatureCheck = $this->config->getSystemValueBool('encryption_skip_signature_check', false);
99
-		$this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
100
-
101
-		if ($skipSignatureCheck) {
102
-			$output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n");
103
-			return 1;
104
-		}
105
-
106
-		if (!$this->util->isMasterKeyEnabled()) {
107
-			$output->writeln("<error>Repairing only works with master key encryption.</error>\n");
108
-			return 1;
109
-		}
110
-
111
-		$user = (string)$input->getArgument('user');
112
-		$pathToWalk = "/$user/files";
113
-
114
-		$pathOption = \trim(($input->getOption('path') ?? ''), '/');
115
-		if ($pathOption !== "") {
116
-			$pathToWalk = "$pathToWalk/$pathOption";
117
-		}
118
-
119
-		if ($user === '') {
120
-			$output->writeln("<error>No user id provided.</error>\n");
121
-			return 1;
122
-		}
123
-
124
-		if ($this->userManager->get($user) === null) {
125
-			$output->writeln("<error>User id $user does not exist. Please provide a valid user id</error>");
126
-			return 1;
127
-		}
128
-		return $this->walkPathOfUser($user, $pathToWalk, $output);
129
-	}
130
-
131
-	/**
132
-	 * @return int 0 for success, 1 for error
133
-	 */
134
-	private function walkPathOfUser(string $user, string $path, OutputInterface $output): int {
135
-		$this->setupUserFs($user);
136
-		if (!$this->view->file_exists($path)) {
137
-			$output->writeln("<error>Path \"$path\" does not exist. Please provide a valid path.</error>");
138
-			return 1;
139
-		}
140
-
141
-		if ($this->view->is_file($path)) {
142
-			$output->writeln("Verifying the content of file \"$path\"");
143
-			$this->verifyFileContent($path, $output);
144
-			return 0;
145
-		}
146
-		$directories = [];
147
-		$directories[] = $path;
148
-		while ($root = \array_pop($directories)) {
149
-			$directoryContent = $this->view->getDirectoryContent($root);
150
-			foreach ($directoryContent as $file) {
151
-				$path = $root . '/' . $file['name'];
152
-				if ($this->view->is_dir($path)) {
153
-					$directories[] = $path;
154
-				} else {
155
-					$output->writeln("Verifying the content of file \"$path\"");
156
-					$this->verifyFileContent($path, $output);
157
-				}
158
-			}
159
-		}
160
-		return 0;
161
-	}
162
-
163
-	/**
164
-	 * @param bool $ignoreCorrectEncVersionCall, setting this variable to false avoids recursion
165
-	 */
166
-	private function verifyFileContent(string $path, OutputInterface $output, bool $ignoreCorrectEncVersionCall = true): bool {
167
-		try {
168
-			/**
169
-			 * In encryption, the files are read in a block size of 8192 bytes
170
-			 * Read block size of 8192 and a bit more (808 bytes)
171
-			 * If there is any problem, the first block should throw the signature
172
-			 * mismatch error. Which as of now, is enough to proceed ahead to
173
-			 * correct the encrypted version.
174
-			 */
175
-			$handle = $this->view->fopen($path, 'rb');
176
-
177
-			if ($handle === false) {
178
-				$output->writeln("<warning>Failed to open file: \"$path\" skipping</warning>");
179
-				return true;
180
-			}
181
-
182
-			if (\fread($handle, 9001) !== false) {
183
-				$fileInfo = $this->view->getFileInfo($path);
184
-				if (!$fileInfo) {
185
-					$output->writeln("<warning>File info not found for file: \"$path\"</warning>");
186
-					return true;
187
-				}
188
-				$encryptedVersion = $fileInfo->getEncryptedVersion();
189
-				$stat = $this->view->stat($path);
190
-				if (($encryptedVersion == 0) && isset($stat['hasHeader']) && ($stat['hasHeader'] == true)) {
191
-					// The file has encrypted to false but has an encryption header
192
-					if ($ignoreCorrectEncVersionCall === true) {
193
-						// Lets rectify the file by correcting encrypted version
194
-						$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
195
-						return $this->correctEncryptedVersion($path, $output);
196
-					}
197
-					return false;
198
-				}
199
-				$output->writeln("<info>The file \"$path\" is: OK</info>");
200
-			}
201
-
202
-			\fclose($handle);
203
-
204
-			return true;
205
-		} catch (ServerNotAvailableException $e) {
206
-			// not a "bad signature" error and likely "legacy cipher" exception
207
-			// this could mean that the file is maybe not encrypted but the encrypted version is set
208
-			if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) {
209
-				$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
210
-				return $this->correctEncryptedVersion($path, $output, true);
211
-			}
212
-			return false;
213
-		} catch (HintException $e) {
214
-			$this->logger->warning("Issue: " . $e->getMessage());
215
-			// If allowOnce is set to false, this becomes recursive.
216
-			if ($ignoreCorrectEncVersionCall === true) {
217
-				// Lets rectify the file by correcting encrypted version
218
-				$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
219
-				return $this->correctEncryptedVersion($path, $output);
220
-			}
221
-			return false;
222
-		}
223
-	}
224
-
225
-	/**
226
-	 * @param bool $includeZero whether to try zero version for unencrypted file
227
-	 */
228
-	private function correctEncryptedVersion(string $path, OutputInterface $output, bool $includeZero = false): bool {
229
-		$fileInfo = $this->view->getFileInfo($path);
230
-		if (!$fileInfo) {
231
-			$output->writeln("<warning>File info not found for file: \"$path\"</warning>");
232
-			return true;
233
-		}
234
-		$fileId = $fileInfo->getId();
235
-		if ($fileId === null) {
236
-			$output->writeln("<warning>File info contains no id for file: \"$path\"</warning>");
237
-			return true;
238
-		}
239
-		$encryptedVersion = $fileInfo->getEncryptedVersion();
240
-		$wrongEncryptedVersion = $encryptedVersion;
241
-
242
-		$storage = $fileInfo->getStorage();
243
-
244
-		$cache = $storage->getCache();
245
-		$fileCache = $cache->get($fileId);
246
-		if (!$fileCache) {
247
-			$output->writeln("<warning>File cache entry not found for file: \"$path\"</warning>");
248
-			return true;
249
-		}
250
-
251
-		if ($storage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
252
-			$output->writeln("<info>The file: \"$path\" is a share. Please also run the script for the owner of the share</info>");
253
-			return true;
254
-		}
255
-
256
-		// Save original encrypted version so we can restore it if decryption fails with all version
257
-		$originalEncryptedVersion = $encryptedVersion;
258
-		if ($encryptedVersion >= 0) {
259
-			if ($includeZero) {
260
-				// try with zero first
261
-				$cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0];
262
-				$cache->put($fileCache->getPath(), $cacheInfo);
263
-				$output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>");
264
-				if ($this->verifyFileContent($path, $output, false) === true) {
265
-					$output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>");
266
-					return true;
267
-				}
268
-			}
269
-
270
-			// Test by decrementing the value till 1 and if nothing works try incrementing
271
-			$encryptedVersion--;
272
-			while ($encryptedVersion > 0) {
273
-				$cacheInfo = ['encryptedVersion' => $encryptedVersion, 'encrypted' => $encryptedVersion];
274
-				$cache->put($fileCache->getPath(), $cacheInfo);
275
-				$output->writeln("<info>Decrement the encrypted version to $encryptedVersion</info>");
276
-				if ($this->verifyFileContent($path, $output, false) === true) {
277
-					$output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . "</info>");
278
-					return true;
279
-				}
280
-				$encryptedVersion--;
281
-			}
282
-
283
-			// So decrementing did not work. Now lets increment. Max increment is till 5
284
-			$increment = 1;
285
-			while ($increment <= 5) {
286
-				/**
287
-				 * The wrongEncryptedVersion would not be incremented so nothing to worry about here.
288
-				 * Only the newEncryptedVersion is incremented.
289
-				 * For example if the wrong encrypted version is 4 then
290
-				 * cycle1 -> newEncryptedVersion = 5 ( 4 + 1)
291
-				 * cycle2 -> newEncryptedVersion = 6 ( 4 + 2)
292
-				 * cycle3 -> newEncryptedVersion = 7 ( 4 + 3)
293
-				 */
294
-				$newEncryptedVersion = $wrongEncryptedVersion + $increment;
295
-
296
-				$cacheInfo = ['encryptedVersion' => $newEncryptedVersion, 'encrypted' => $newEncryptedVersion];
297
-				$cache->put($fileCache->getPath(), $cacheInfo);
298
-				$output->writeln("<info>Increment the encrypted version to $newEncryptedVersion</info>");
299
-				if ($this->verifyFileContent($path, $output, false) === true) {
300
-					$output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . "</info>");
301
-					return true;
302
-				}
303
-				$increment++;
304
-			}
305
-		}
306
-
307
-		$cacheInfo = ['encryptedVersion' => $originalEncryptedVersion, 'encrypted' => $originalEncryptedVersion];
308
-		$cache->put($fileCache->getPath(), $cacheInfo);
309
-		$output->writeln("<info>No fix found for \"$path\", restored version to original: $originalEncryptedVersion</info>");
310
-
311
-		return false;
312
-	}
313
-
314
-	/**
315
-	 * Setup user file system
316
-	 */
317
-	private function setupUserFs(string $uid): void {
318
-		\OC_Util::tearDownFS();
319
-		\OC_Util::setupFS($uid);
320
-	}
39
+    /** @var IConfig */
40
+    private $config;
41
+
42
+    /** @var ILogger */
43
+    private $logger;
44
+
45
+    /** @var IRootFolder  */
46
+    private $rootFolder;
47
+
48
+    /** @var IUserManager  */
49
+    private $userManager;
50
+
51
+    /** @var Util */
52
+    private $util;
53
+
54
+    /** @var View  */
55
+    private $view;
56
+
57
+    /** @var bool */
58
+    private $supportLegacy;
59
+
60
+    public function __construct(
61
+        IConfig $config,
62
+        ILogger $logger,
63
+        IRootFolder $rootFolder,
64
+        IUserManager $userManager,
65
+        Util $util,
66
+        View $view
67
+    ) {
68
+        $this->config = $config;
69
+        $this->logger = $logger;
70
+        $this->rootFolder = $rootFolder;
71
+        $this->userManager = $userManager;
72
+        $this->util = $util;
73
+        $this->view = $view;
74
+        $this->supportLegacy = false;
75
+
76
+        parent::__construct();
77
+    }
78
+
79
+    protected function configure(): void {
80
+        parent::configure();
81
+
82
+        $this
83
+            ->setName('encryption:fix-encrypted-version')
84
+            ->setDescription('Fix the encrypted version if the encrypted file(s) are not downloadable.')
85
+            ->addArgument(
86
+                'user',
87
+                InputArgument::REQUIRED,
88
+                'The id of the user whose files need fixing'
89
+            )->addOption(
90
+                'path',
91
+                'p',
92
+                InputArgument::OPTIONAL,
93
+                'Limit files to fix with path, e.g., --path="/Music/Artist". If path indicates a directory, all the files inside directory will be fixed.'
94
+            );
95
+    }
96
+
97
+    protected function execute(InputInterface $input, OutputInterface $output): int {
98
+        $skipSignatureCheck = $this->config->getSystemValueBool('encryption_skip_signature_check', false);
99
+        $this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
100
+
101
+        if ($skipSignatureCheck) {
102
+            $output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n");
103
+            return 1;
104
+        }
105
+
106
+        if (!$this->util->isMasterKeyEnabled()) {
107
+            $output->writeln("<error>Repairing only works with master key encryption.</error>\n");
108
+            return 1;
109
+        }
110
+
111
+        $user = (string)$input->getArgument('user');
112
+        $pathToWalk = "/$user/files";
113
+
114
+        $pathOption = \trim(($input->getOption('path') ?? ''), '/');
115
+        if ($pathOption !== "") {
116
+            $pathToWalk = "$pathToWalk/$pathOption";
117
+        }
118
+
119
+        if ($user === '') {
120
+            $output->writeln("<error>No user id provided.</error>\n");
121
+            return 1;
122
+        }
123
+
124
+        if ($this->userManager->get($user) === null) {
125
+            $output->writeln("<error>User id $user does not exist. Please provide a valid user id</error>");
126
+            return 1;
127
+        }
128
+        return $this->walkPathOfUser($user, $pathToWalk, $output);
129
+    }
130
+
131
+    /**
132
+     * @return int 0 for success, 1 for error
133
+     */
134
+    private function walkPathOfUser(string $user, string $path, OutputInterface $output): int {
135
+        $this->setupUserFs($user);
136
+        if (!$this->view->file_exists($path)) {
137
+            $output->writeln("<error>Path \"$path\" does not exist. Please provide a valid path.</error>");
138
+            return 1;
139
+        }
140
+
141
+        if ($this->view->is_file($path)) {
142
+            $output->writeln("Verifying the content of file \"$path\"");
143
+            $this->verifyFileContent($path, $output);
144
+            return 0;
145
+        }
146
+        $directories = [];
147
+        $directories[] = $path;
148
+        while ($root = \array_pop($directories)) {
149
+            $directoryContent = $this->view->getDirectoryContent($root);
150
+            foreach ($directoryContent as $file) {
151
+                $path = $root . '/' . $file['name'];
152
+                if ($this->view->is_dir($path)) {
153
+                    $directories[] = $path;
154
+                } else {
155
+                    $output->writeln("Verifying the content of file \"$path\"");
156
+                    $this->verifyFileContent($path, $output);
157
+                }
158
+            }
159
+        }
160
+        return 0;
161
+    }
162
+
163
+    /**
164
+     * @param bool $ignoreCorrectEncVersionCall, setting this variable to false avoids recursion
165
+     */
166
+    private function verifyFileContent(string $path, OutputInterface $output, bool $ignoreCorrectEncVersionCall = true): bool {
167
+        try {
168
+            /**
169
+             * In encryption, the files are read in a block size of 8192 bytes
170
+             * Read block size of 8192 and a bit more (808 bytes)
171
+             * If there is any problem, the first block should throw the signature
172
+             * mismatch error. Which as of now, is enough to proceed ahead to
173
+             * correct the encrypted version.
174
+             */
175
+            $handle = $this->view->fopen($path, 'rb');
176
+
177
+            if ($handle === false) {
178
+                $output->writeln("<warning>Failed to open file: \"$path\" skipping</warning>");
179
+                return true;
180
+            }
181
+
182
+            if (\fread($handle, 9001) !== false) {
183
+                $fileInfo = $this->view->getFileInfo($path);
184
+                if (!$fileInfo) {
185
+                    $output->writeln("<warning>File info not found for file: \"$path\"</warning>");
186
+                    return true;
187
+                }
188
+                $encryptedVersion = $fileInfo->getEncryptedVersion();
189
+                $stat = $this->view->stat($path);
190
+                if (($encryptedVersion == 0) && isset($stat['hasHeader']) && ($stat['hasHeader'] == true)) {
191
+                    // The file has encrypted to false but has an encryption header
192
+                    if ($ignoreCorrectEncVersionCall === true) {
193
+                        // Lets rectify the file by correcting encrypted version
194
+                        $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
195
+                        return $this->correctEncryptedVersion($path, $output);
196
+                    }
197
+                    return false;
198
+                }
199
+                $output->writeln("<info>The file \"$path\" is: OK</info>");
200
+            }
201
+
202
+            \fclose($handle);
203
+
204
+            return true;
205
+        } catch (ServerNotAvailableException $e) {
206
+            // not a "bad signature" error and likely "legacy cipher" exception
207
+            // this could mean that the file is maybe not encrypted but the encrypted version is set
208
+            if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) {
209
+                $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
210
+                return $this->correctEncryptedVersion($path, $output, true);
211
+            }
212
+            return false;
213
+        } catch (HintException $e) {
214
+            $this->logger->warning("Issue: " . $e->getMessage());
215
+            // If allowOnce is set to false, this becomes recursive.
216
+            if ($ignoreCorrectEncVersionCall === true) {
217
+                // Lets rectify the file by correcting encrypted version
218
+                $output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
219
+                return $this->correctEncryptedVersion($path, $output);
220
+            }
221
+            return false;
222
+        }
223
+    }
224
+
225
+    /**
226
+     * @param bool $includeZero whether to try zero version for unencrypted file
227
+     */
228
+    private function correctEncryptedVersion(string $path, OutputInterface $output, bool $includeZero = false): bool {
229
+        $fileInfo = $this->view->getFileInfo($path);
230
+        if (!$fileInfo) {
231
+            $output->writeln("<warning>File info not found for file: \"$path\"</warning>");
232
+            return true;
233
+        }
234
+        $fileId = $fileInfo->getId();
235
+        if ($fileId === null) {
236
+            $output->writeln("<warning>File info contains no id for file: \"$path\"</warning>");
237
+            return true;
238
+        }
239
+        $encryptedVersion = $fileInfo->getEncryptedVersion();
240
+        $wrongEncryptedVersion = $encryptedVersion;
241
+
242
+        $storage = $fileInfo->getStorage();
243
+
244
+        $cache = $storage->getCache();
245
+        $fileCache = $cache->get($fileId);
246
+        if (!$fileCache) {
247
+            $output->writeln("<warning>File cache entry not found for file: \"$path\"</warning>");
248
+            return true;
249
+        }
250
+
251
+        if ($storage->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) {
252
+            $output->writeln("<info>The file: \"$path\" is a share. Please also run the script for the owner of the share</info>");
253
+            return true;
254
+        }
255
+
256
+        // Save original encrypted version so we can restore it if decryption fails with all version
257
+        $originalEncryptedVersion = $encryptedVersion;
258
+        if ($encryptedVersion >= 0) {
259
+            if ($includeZero) {
260
+                // try with zero first
261
+                $cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0];
262
+                $cache->put($fileCache->getPath(), $cacheInfo);
263
+                $output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>");
264
+                if ($this->verifyFileContent($path, $output, false) === true) {
265
+                    $output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>");
266
+                    return true;
267
+                }
268
+            }
269
+
270
+            // Test by decrementing the value till 1 and if nothing works try incrementing
271
+            $encryptedVersion--;
272
+            while ($encryptedVersion > 0) {
273
+                $cacheInfo = ['encryptedVersion' => $encryptedVersion, 'encrypted' => $encryptedVersion];
274
+                $cache->put($fileCache->getPath(), $cacheInfo);
275
+                $output->writeln("<info>Decrement the encrypted version to $encryptedVersion</info>");
276
+                if ($this->verifyFileContent($path, $output, false) === true) {
277
+                    $output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . "</info>");
278
+                    return true;
279
+                }
280
+                $encryptedVersion--;
281
+            }
282
+
283
+            // So decrementing did not work. Now lets increment. Max increment is till 5
284
+            $increment = 1;
285
+            while ($increment <= 5) {
286
+                /**
287
+                 * The wrongEncryptedVersion would not be incremented so nothing to worry about here.
288
+                 * Only the newEncryptedVersion is incremented.
289
+                 * For example if the wrong encrypted version is 4 then
290
+                 * cycle1 -> newEncryptedVersion = 5 ( 4 + 1)
291
+                 * cycle2 -> newEncryptedVersion = 6 ( 4 + 2)
292
+                 * cycle3 -> newEncryptedVersion = 7 ( 4 + 3)
293
+                 */
294
+                $newEncryptedVersion = $wrongEncryptedVersion + $increment;
295
+
296
+                $cacheInfo = ['encryptedVersion' => $newEncryptedVersion, 'encrypted' => $newEncryptedVersion];
297
+                $cache->put($fileCache->getPath(), $cacheInfo);
298
+                $output->writeln("<info>Increment the encrypted version to $newEncryptedVersion</info>");
299
+                if ($this->verifyFileContent($path, $output, false) === true) {
300
+                    $output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . "</info>");
301
+                    return true;
302
+                }
303
+                $increment++;
304
+            }
305
+        }
306
+
307
+        $cacheInfo = ['encryptedVersion' => $originalEncryptedVersion, 'encrypted' => $originalEncryptedVersion];
308
+        $cache->put($fileCache->getPath(), $cacheInfo);
309
+        $output->writeln("<info>No fix found for \"$path\", restored version to original: $originalEncryptedVersion</info>");
310
+
311
+        return false;
312
+    }
313
+
314
+    /**
315
+     * Setup user file system
316
+     */
317
+    private function setupUserFs(string $uid): void {
318
+        \OC_Util::tearDownFS();
319
+        \OC_Util::setupFS($uid);
320
+    }
321 321
 }
Please login to merge, or discard this patch.