1<?php 2/** 3 * @author Björn Schießle <bjoern@schiessle.org> 4 * @author Clark Tomlinson <fallen013@gmail.com> 5 * @author Lukas Reschke <lukas@statuscode.ch> 6 * @author Thomas Müller <thomas.mueller@tmit.eu> 7 * @author Vincent Petry <pvince81@owncloud.com> 8 * 9 * @copyright Copyright (c) 2019, ownCloud GmbH 10 * @license AGPL-3.0 11 * 12 * This code is free software: you can redistribute it and/or modify 13 * it under the terms of the GNU Affero General Public License, version 3, 14 * as published by the Free Software Foundation. 15 * 16 * This program 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 License, version 3, 22 * along with this program. If not, see <http://www.gnu.org/licenses/> 23 * 24 */ 25namespace OCA\Encryption; 26 27use OC\Encryption\Exceptions\DecryptionFailedException; 28use OC\Files\View; 29use OCA\Encryption\Crypto\Encryption; 30use OCA\Encryption\Exceptions\PrivateKeyMissingException; 31use OCA\Encryption\Exceptions\PublicKeyMissingException; 32use OCA\Encryption\Crypto\Crypt; 33use OCP\Encryption\Keys\IStorage; 34use OCP\IConfig; 35use OCP\ILogger; 36use OCP\IUserSession; 37 38class KeyManager { 39 40 /** 41 * @var Session 42 */ 43 protected $session; 44 /** 45 * @var IStorage 46 */ 47 private $keyStorage; 48 /** 49 * @var Crypt 50 */ 51 private $crypt; 52 /** 53 * @var string 54 */ 55 private $recoveryKeyId; 56 /** 57 * @var string 58 */ 59 private $publicShareKeyId; 60 /** 61 * @var string 62 */ 63 private $masterKeyId; 64 /** 65 * @var string UserID 66 */ 67 private $keyId; 68 /** 69 * @var string 70 */ 71 private $publicKeyId = 'publicKey'; 72 /** 73 * @var string 74 */ 75 private $privateKeyId = 'privateKey'; 76 77 /** 78 * @var string 79 */ 80 private $shareKeyId = 'shareKey'; 81 82 /** 83 * @var string 84 */ 85 private $fileKeyId = 'fileKey'; 86 /** 87 * @var IConfig 88 */ 89 private $config; 90 /** 91 * @var ILogger 92 */ 93 private $log; 94 /** 95 * @var Util 96 */ 97 private $util; 98 99 /** 100 * @param IStorage $keyStorage 101 * @param Crypt $crypt 102 * @param IConfig $config 103 * @param IUserSession $userSession 104 * @param Session $session 105 * @param ILogger $log 106 * @param Util $util 107 */ 108 public function __construct( 109 IStorage $keyStorage, 110 Crypt $crypt, 111 IConfig $config, 112 IUserSession $userSession, 113 Session $session, 114 ILogger $log, 115 Util $util 116 ) { 117 $this->util = $util; 118 $this->session = $session; 119 $this->keyStorage = $keyStorage; 120 $this->crypt = $crypt; 121 $this->config = $config; 122 $this->log = $log; 123 124 $this->recoveryKeyId = $this->config->getAppValue('encryption', 125 'recoveryKeyId'); 126 if (empty($this->recoveryKeyId)) { 127 $this->recoveryKeyId = 'recoveryKey_' . \substr(\md5((string)\time()), 0, 8); 128 $this->config->setAppValue('encryption', 129 'recoveryKeyId', 130 $this->recoveryKeyId); 131 } 132 133 $this->setPublicShareKeyIDAndMasterKeyId(); 134 135 $this->keyId = $userSession !== null && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false; 136 $this->log = $log; 137 } 138 139 /** 140 * check if key pair for public link shares exists, if not we create one 141 */ 142 public function validateShareKey() { 143 $shareKey = $this->getPublicShareKey(); 144 if (empty($shareKey)) { 145 $keyPair = $this->crypt->createKeyPair(); 146 147 // Save public key 148 $this->keyStorage->setSystemUserKey( 149 $this->publicShareKeyId . '.publicKey', $keyPair['publicKey'], 150 Encryption::ID); 151 152 // Encrypt private key empty passphrase 153 $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], ''); 154 $header = $this->crypt->generateHeader(); 155 $this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey); 156 } 157 } 158 159 /** 160 * check if a key pair for the master key exists, if not we create one 161 */ 162 public function validateMasterKey() { 163 if ($this->util->isMasterKeyEnabled() === false) { 164 return; 165 } 166 167 $masterKey = $this->getPublicMasterKey(); 168 if (empty($masterKey)) { 169 $keyPair = $this->crypt->createKeyPair(); 170 171 // Save public key 172 $this->keyStorage->setSystemUserKey( 173 $this->masterKeyId . '.publicKey', $keyPair['publicKey'], 174 Encryption::ID); 175 176 // Encrypt private key with system password 177 $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId); 178 $header = $this->crypt->generateHeader(); 179 $this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey); 180 } 181 } 182 183 /** 184 * @return bool 185 */ 186 public function recoveryKeyExists() { 187 $key = $this->getRecoveryKey(); 188 return (!empty($key)); 189 } 190 191 /** 192 * get recovery key 193 * 194 * @return string 195 */ 196 public function getRecoveryKey() { 197 return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID); 198 } 199 200 /** 201 * get recovery key ID 202 * 203 * @return string 204 */ 205 public function getRecoveryKeyId() { 206 return $this->recoveryKeyId; 207 } 208 209 /** 210 * @param string $password 211 * @return bool 212 */ 213 public function checkRecoveryPassword($password) { 214 $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID); 215 $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); 216 217 if ($decryptedRecoveryKey) { 218 return true; 219 } 220 return false; 221 } 222 223 /** 224 * @param string $uid 225 * @param string $password 226 * @param array $keyPair 227 * @return bool 228 */ 229 public function storeKeyPair($uid, $password, $keyPair) { 230 // Save Public Key 231 $this->setPublicKey($uid, $keyPair['publicKey']); 232 233 $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid); 234 235 $header = $this->crypt->generateHeader(); 236 237 if ($encryptedKey) { 238 $this->setPrivateKey($uid, $header . $encryptedKey); 239 return true; 240 } 241 return false; 242 } 243 244 /** 245 * @param string $password 246 * @param array $keyPair 247 * @return bool 248 */ 249 public function setRecoveryKey($password, $keyPair) { 250 // Save Public Key 251 $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId() . 252 '.publicKey', 253 $keyPair['publicKey'], 254 Encryption::ID); 255 256 $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password); 257 $header = $this->crypt->generateHeader(); 258 259 if ($encryptedKey) { 260 $this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey); 261 return true; 262 } 263 return false; 264 } 265 266 /** 267 * @param string $userId 268 * @param string $key 269 * @return bool 270 */ 271 public function setPublicKey($userId, $key) { 272 return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID); 273 } 274 275 /** 276 * @param string $userId 277 * @param string $key 278 * @return bool 279 */ 280 public function setPrivateKey($userId, $key) { 281 return $this->keyStorage->setUserKey($userId, 282 $this->privateKeyId, 283 $key, 284 Encryption::ID); 285 } 286 287 /** 288 * write file key to key storage 289 * 290 * @param string $path 291 * @param string $key 292 * @return boolean 293 */ 294 public function setFileKey($path, $key) { 295 return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID); 296 } 297 298 /** 299 * set all file keys (the file key and the corresponding share keys) 300 * 301 * @param string $path 302 * @param array $keys 303 */ 304 public function setAllFileKeys($path, $keys) { 305 $this->setFileKey($path, $keys['data']); 306 foreach ($keys['keys'] as $uid => $keyFile) { 307 $this->setShareKey($path, $uid, $keyFile); 308 } 309 } 310 311 /** 312 * write share key to the key storage 313 * 314 * @param string $path 315 * @param string $uid 316 * @param string $key 317 * @return boolean 318 */ 319 public function setShareKey($path, $uid, $key) { 320 $keyId = $uid . '.' . $this->shareKeyId; 321 return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID); 322 } 323 324 /** 325 * Decrypt private key and store it 326 * 327 * @param string $uid user id 328 * @param string $passPhrase users password 329 * @return boolean 330 */ 331 public function init($uid, $passPhrase) { 332 $this->session->setStatus(Session::INIT_EXECUTED); 333 334 try { 335 if ($this->util->isMasterKeyEnabled()) { 336 $uid = $this->getMasterKeyId(); 337 $passPhrase = $this->getMasterKeyPassword(); 338 $privateKey = $this->getSystemPrivateKey($uid); 339 } else { 340 $privateKey = $this->getPrivateKey($uid); 341 } 342 $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); 343 } catch (PrivateKeyMissingException $e) { 344 return false; 345 } catch (DecryptionFailedException $e) { 346 return false; 347 } catch (\Exception $e) { 348 $this->log->warning( 349 'Could not decrypt the private key from user "' . $uid . '"" during login. ' . 350 'Assume password change on the user back-end. Error message: ' 351 . $e->getMessage() 352 ); 353 return false; 354 } 355 356 if ($privateKey) { 357 $this->session->setPrivateKey($privateKey); 358 $this->session->setStatus(Session::INIT_SUCCESSFUL); 359 return true; 360 } 361 362 return false; 363 } 364 365 /** 366 * @param string $userId 367 * @return string 368 * @throws PrivateKeyMissingException 369 */ 370 public function getPrivateKey($userId) { 371 $privateKey = $this->keyStorage->getUserKey($userId, 372 $this->privateKeyId, Encryption::ID); 373 374 if (\strlen($privateKey) !== 0) { 375 return $privateKey; 376 } 377 throw new PrivateKeyMissingException($userId); 378 } 379 380 /** 381 * @param string $path 382 * @param string $uid 383 * @return string 384 */ 385 public function getFileKey($path, $uid) { 386 if ($uid === '') { 387 $uid = null; 388 } 389 $publicAccess = ($uid === null); 390 $encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID); 391 392 if (empty($encryptedFileKey)) { 393 return ''; 394 } 395 396 if ($this->util->isMasterKeyEnabled()) { 397 $uid = $this->getMasterKeyId(); 398 $shareKey = $this->getShareKey($path, $uid); 399 if ($publicAccess) { 400 $privateKey = $this->getSystemPrivateKey($uid); 401 $privateKey = $this->crypt->decryptPrivateKey($privateKey, $this->getMasterKeyPassword(), $uid); 402 } else { 403 // when logged in, the master key is already decrypted in the session 404 $privateKey = $this->session->getPrivateKey(); 405 } 406 } elseif ($publicAccess) { 407 // use public share key for public links 408 $uid = $this->getPublicShareKeyId(); 409 $shareKey = $this->getShareKey($path, $uid); 410 $privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID); 411 $privateKey = $this->crypt->decryptPrivateKey($privateKey); 412 } else { 413 $shareKey = $this->getShareKey($path, $uid); 414 $privateKey = $this->session->getPrivateKey(); 415 } 416 417 if ($encryptedFileKey && $shareKey && $privateKey) { 418 return $this->crypt->multiKeyDecrypt($encryptedFileKey, 419 $shareKey, 420 $privateKey); 421 } 422 423 return ''; 424 } 425 426 /** 427 * Get the current version of a file 428 * 429 * @param string $path 430 * @param View $view 431 * @return int 432 */ 433 public function getVersion($path, View $view) { 434 $fileInfo = $view->getFileInfo($path); 435 if ($fileInfo === false) { 436 return 0; 437 } 438 return $fileInfo->getEncryptedVersion(); 439 } 440 441 /** 442 * Set the current version of a file 443 * 444 * @param string $path 445 * @param int $version 446 * @param View $view 447 */ 448 public function setVersion($path, $version, View $view) { 449 $fileInfo = $view->getFileInfo($path); 450 451 if ($fileInfo !== false) { 452 $cache = $fileInfo->getStorage()->getCache(); 453 $cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]); 454 } 455 } 456 457 /** 458 * get the encrypted file key 459 * 460 * @param string $path 461 * @return string 462 */ 463 public function getEncryptedFileKey($path) { 464 $encryptedFileKey = $this->keyStorage->getFileKey($path, 465 $this->fileKeyId, Encryption::ID); 466 467 return $encryptedFileKey; 468 } 469 470 /** 471 * delete share key 472 * 473 * @param string $path 474 * @param string $keyId 475 * @return boolean 476 */ 477 public function deleteShareKey($path, $keyId) { 478 return $this->keyStorage->deleteFileKey( 479 $path, 480 $keyId . '.' . $this->shareKeyId, 481 Encryption::ID); 482 } 483 484 /** 485 * @param string $path 486 * @param string $uid 487 * @return mixed 488 */ 489 public function getShareKey($path, $uid) { 490 $keyId = $uid . '.' . $this->shareKeyId; 491 return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID); 492 } 493 494 /** 495 * check if user has a private and a public key 496 * 497 * @param string $userId 498 * @return bool 499 * @throws PrivateKeyMissingException 500 * @throws PublicKeyMissingException 501 */ 502 public function userHasKeys($userId) { 503 $privateKey = $publicKey = true; 504 $exception = null; 505 506 try { 507 $this->getPrivateKey($userId); 508 } catch (PrivateKeyMissingException $e) { 509 $privateKey = false; 510 $exception = $e; 511 } 512 try { 513 $this->getPublicKey($userId); 514 } catch (PublicKeyMissingException $e) { 515 $publicKey = false; 516 $exception = $e; 517 } 518 519 if ($privateKey && $publicKey) { 520 return true; 521 } elseif (!$privateKey && !$publicKey) { 522 return false; 523 } else { 524 throw $exception; 525 } 526 } 527 528 /** 529 * @param string $userId 530 * @return mixed 531 * @throws PublicKeyMissingException 532 */ 533 public function getPublicKey($userId) { 534 $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId, Encryption::ID); 535 536 if (\strlen($publicKey) !== 0) { 537 return $publicKey; 538 } 539 throw new PublicKeyMissingException($userId); 540 } 541 542 public function getPublicShareKeyId() { 543 return $this->publicShareKeyId; 544 } 545 546 /** 547 * get public key for public link shares 548 * 549 * @return string 550 */ 551 public function getPublicShareKey() { 552 return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID); 553 } 554 555 /** 556 * @param string $purpose 557 * @param bool $timestamp 558 * @param bool $includeUserKeys 559 */ 560 public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) { 561// $backupDir = $this->keyStorage->; 562 } 563 564 /** 565 * creat a backup of the users private and public key and then delete it 566 * 567 * @param string $uid 568 */ 569 public function deleteUserKeys($uid) { 570 $this->backupAllKeys('password_reset'); 571 $this->deletePublicKey($uid); 572 $this->deletePrivateKey($uid); 573 } 574 575 /** 576 * @param string $uid 577 * @return bool 578 */ 579 public function deletePublicKey($uid) { 580 return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId, Encryption::ID); 581 } 582 583 /** 584 * @param string $uid 585 * @return bool 586 */ 587 private function deletePrivateKey($uid) { 588 return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID); 589 } 590 591 /** 592 * @param string $path 593 * @return bool 594 */ 595 public function deleteAllFileKeys($path) { 596 return $this->keyStorage->deleteAllFileKeys($path); 597 } 598 599 /** 600 * @param array $userIds 601 * @return array 602 * @throws PublicKeyMissingException 603 */ 604 public function getPublicKeys(array $userIds) { 605 $keys = []; 606 607 foreach ($userIds as $userId) { 608 try { 609 $keys[$userId] = $this->getPublicKey($userId); 610 } catch (PublicKeyMissingException $e) { 611 continue; 612 } 613 } 614 615 return $keys; 616 } 617 618 /** 619 * @param string $keyId 620 * @return string returns openssl key 621 */ 622 public function getSystemPrivateKey($keyId) { 623 return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID); 624 } 625 626 /** 627 * @param string $keyId 628 * @param string $key 629 * @return string returns openssl key 630 */ 631 public function setSystemPrivateKey($keyId, $key) { 632 return $this->keyStorage->setSystemUserKey( 633 $keyId . '.' . $this->privateKeyId, 634 $key, 635 Encryption::ID); 636 } 637 638 /** 639 * add system keys such as the public share key and the recovery key 640 * 641 * @param array $accessList 642 * @param array $publicKeys 643 * @param string $uid 644 * @return array 645 * @throws PublicKeyMissingException 646 */ 647 public function addSystemKeys(array $accessList, array $publicKeys, $uid) { 648 if (!empty($accessList['public'])) { 649 $publicShareKey = $this->getPublicShareKey(); 650 if (empty($publicShareKey)) { 651 throw new PublicKeyMissingException($this->getPublicShareKeyId()); 652 } 653 $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey; 654 } 655 656 if ($this->recoveryKeyExists() && 657 $this->util->isRecoveryEnabledForUser($uid)) { 658 $publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey(); 659 } 660 661 return $publicKeys; 662 } 663 664 /** 665 * get master key password 666 * 667 * @return string 668 * @throws \Exception 669 */ 670 public function getMasterKeyPassword() { 671 $password = $this->config->getSystemValue('secret'); 672 if (empty($password)) { 673 throw new \Exception('Can not get secret from ownCloud instance'); 674 } 675 676 return $password; 677 } 678 679 /** 680 * return master key id 681 * 682 * @return string 683 */ 684 public function getMasterKeyId() { 685 if ($this->config->getAppValue('encryption', 'masterKeyId') !== $this->masterKeyId) { 686 $this->masterKeyId = $this->config->getAppValue('encryption', 'masterKeyId'); 687 } 688 return $this->masterKeyId; 689 } 690 691 /** 692 * get public master key 693 * 694 * @return string 695 */ 696 public function getPublicMasterKey() { 697 return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID); 698 } 699 700 /** 701 * set publicShareKeyId and masterKeyId if not set 702 */ 703 public function setPublicShareKeyIDAndMasterKeyId() { 704 $this->publicShareKeyId = $this->config->getAppValue('encryption', 705 'publicShareKeyId'); 706 if (($this->publicShareKeyId === null) || ($this->publicShareKeyId === '')) { 707 $this->publicShareKeyId = 'pubShare_' . \substr(\md5((string)\time()), 0, 8); 708 $this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId); 709 } 710 711 $this->masterKeyId = $this->config->getAppValue('encryption', 712 'masterKeyId'); 713 if (($this->masterKeyId === null) || ($this->masterKeyId === '')) { 714 $this->masterKeyId = 'master_' . \substr(\md5((string)\time()), 0, 8); 715 $this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId); 716 } 717 } 718} 719