-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39935 from owncloud/sftp_key_handling
Sftp key handling
- Loading branch information
Showing
12 changed files
with
364 additions
and
61 deletions.
There are no files selected for viewing
106 changes: 106 additions & 0 deletions
106
apps/files_external/appinfo/Migrations/Version20220329110116.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
namespace OCA\files_external\Migrations; | ||
|
||
use OC\NeedsUpdateException; | ||
use OCA\Files_External\Lib\Backend\SFTP; | ||
use OCA\Files_External\Lib\Auth\PublicKey\RSA; | ||
use OCA\Files_External\Lib\RSAStore; | ||
use OCP\Migration\ISimpleMigration; | ||
use OCP\Migration\IOutput; | ||
use OCP\Files\External\Service\IGlobalStoragesService; | ||
use OCP\Files\External\IStorageConfig; | ||
use OCP\ILogger; | ||
use OCP\IConfig; | ||
use phpseclib3\Crypt\RSA as RSACrypt; | ||
use phpseclib3\Crypt\RSA\PrivateKey; | ||
|
||
class Version20220329110116 implements ISimpleMigration { | ||
/** @var IGlobalStoragesService */ | ||
private $storageService; | ||
/** @var ILogger */ | ||
private $logger; | ||
/** @var IConfig */ | ||
private $config; | ||
|
||
public function __construct(IGlobalStoragesService $storageService, ILogger $logger, IConfig $config) { | ||
$this->storageService = $storageService; | ||
$this->logger = $logger; | ||
$this->config = $config; | ||
} | ||
/** | ||
* @param IOutput $out | ||
*/ | ||
public function run(IOutput $out) { | ||
if (!$this->config->getSystemValue('installed', false)) { | ||
// Skip the migration for new installations -> nothing to migrate | ||
return; | ||
} | ||
|
||
$this->loadFSApps(); | ||
\OC_Util::setupFS(); // this should load additional backends and auth mechanisms | ||
$storageConfigs = $this->storageService->getStorageForAllUsers(); | ||
$pass = $this->config->getSystemValue('secret', ''); | ||
|
||
$rsaStore = RSAStore::getGlobalInstance(); | ||
foreach ($storageConfigs as $storageConfig) { | ||
if ($storageConfig->getBackend() instanceof SFTP && $storageConfig->getAuthMechanism() instanceof RSA) { | ||
$encPubKey = $storageConfig->getBackendOption('public_key'); | ||
$encPrivKey = $storageConfig->getBackendOption('private_key'); | ||
|
||
$pubKey = \base64_decode($encPubKey, true); | ||
$privKey = \base64_decode($encPrivKey, true); | ||
|
||
$configId = $storageConfig->getId(); | ||
if ($pubKey === false || $privKey === false) { | ||
$out->warning("Storage configuration with id = {$configId}: Cannot decode either public or private key, skipping"); | ||
continue; | ||
} | ||
|
||
try { | ||
$rsaKey = RSACrypt::load($privKey, $pass)->withHash('sha1'); | ||
} catch (\phpseclib3\Exception\NoKeyLoadedException $e) { | ||
$out->warning("Storage configuration with id = {$configId}: Cannot load private key, skipping"); | ||
continue; | ||
} | ||
|
||
$targetUserId = ''; | ||
if ($storageConfig->getType() === IStorageConfig::MOUNT_TYPE_PERSONAl) { | ||
$applicableUsers = $storageConfig->getApplicableUsers(); | ||
$targetUserId = $applicableUsers[0]; // it must have one user. | ||
} | ||
|
||
$token = $rsaStore->storeData($rsaKey, $targetUserId); | ||
$storageConfig->setBackendOption('public_key', $pubKey); | ||
$storageConfig->setBackendOption('private_key', $token); | ||
|
||
$this->storageService->updateStorage($storageConfig); | ||
$out->info("Storage configuration with id = {$configId}: keys migrated successfully"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Load the FS apps. This is required because the FS apps might not be loaded during the | ||
* migration. | ||
*/ | ||
private function loadFSApps() { | ||
$enabledApps = \OC_App::getEnabledApps(); | ||
foreach ($enabledApps as $enabledApp) { | ||
if ($enabledApp !== 'files_external' && \OC_App::isType($enabledApp, ['filesystem'])) { | ||
try { | ||
\OC_App::loadApp($enabledApp); | ||
} catch (NeedsUpdateException $ex) { | ||
if (\OC_App::updateApp($enabledApp)) { | ||
// update successful. | ||
// We can load the app without checking if the should upgrade or not. | ||
\OC_App::loadApp($enabledApp, false); | ||
} else { | ||
$this->logger->error("Error during files_external migration. $enabledApp couldn't be loaded nor updated.", ['app' => 'files_external']); | ||
$this->logger->logException($ex, ['app' => 'files_external']); | ||
$this->logger->error("Mount points using $enabledApp might not be migrated properly. You might need to re-enter the passwords for those mount points", ['app' => 'files_external']); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php | ||
/** | ||
* @author Juan Pablo Villafáñez Ramos <[email protected]> | ||
* | ||
* @copyright Copyright (c) 2022, ownCloud GmbH | ||
* @license AGPL-3.0 | ||
* | ||
* This code is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License, version 3, | ||
* as published by the Free Software Foundation. | ||
* | ||
* This program 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, version 3, | ||
* along with this program. If not, see <http://www.gnu.org/licenses/> | ||
* | ||
*/ | ||
|
||
namespace OCA\Files_External\Lib; | ||
|
||
use OCP\Security\ICredentialsManager; | ||
use OCP\IConfig; | ||
use phpseclib3\Crypt\RSA; | ||
use phpseclib3\Crypt\RSA\PrivateKey; | ||
|
||
/** | ||
* Store and retrieve phpseclib3 RSA private keys | ||
*/ | ||
class RSAStore { | ||
private static $rsaStore = null; | ||
|
||
/** @var ICredentialsManager */ | ||
private $credentialsManager; | ||
/** @var IConfig */ | ||
private $config; | ||
|
||
/** | ||
* Get the global instance of the RSAStore. If no one is set yet, a new | ||
* one will be created using real server components. | ||
* @return RSAStore | ||
*/ | ||
public static function getGlobalInstance(): RSAStore { | ||
if (self::$rsaStore === null) { | ||
self::$rsaStore = new RSAStore( | ||
\OC::$server->getCredentialsManager(), | ||
\OC::$server->getConfig() | ||
); | ||
} | ||
return self::$rsaStore; | ||
} | ||
|
||
/** | ||
* Set a new RSAStore instance as a global instance overwriting whatever | ||
* instance was there. | ||
* This shouldn't be needed outside of unit tests | ||
* @param RSAStore|null The RSAStore to be set as global instance, or null | ||
* to destroy the global instance (destroying the global instance will allow | ||
* getting the default one again) | ||
*/ | ||
public static function setGlobalInstance(?RSAStore $rsaStore) { | ||
self::$rsaStore = $rsaStore; | ||
} | ||
|
||
/** | ||
* @param ICredentialsManager $credentialsManager | ||
* @param IConfig $config | ||
*/ | ||
public function __construct(ICredentialsManager $credentialsManager, IConfig $config) { | ||
$this->credentialsManager = $credentialsManager; | ||
$this->config = $config; | ||
} | ||
|
||
/** | ||
* Store the $rsaKey inside the $userId's space. A token will be returned | ||
* in order to retrieve the stored key | ||
* @param PrivateKey $rsaKey the private key to be stored | ||
* @param string $userId the user under which the token will be stored | ||
* @return string an opaque token to be used to retrieve the stored key later | ||
*/ | ||
public function storeData(PrivateKey $rsaKey, string $userId): string { | ||
$password = $this->config->getSystemValue('secret', ''); | ||
$privatekey = $rsaKey->withPassword($password)->toString('PKCS1'); | ||
|
||
$keyId = \uniqid('rsaid:', true); | ||
|
||
$this->credentialsManager->store($userId, $keyId, $privatekey); | ||
|
||
$keyData = [ | ||
'rsaId' => $keyId, | ||
'userId' => $userId, | ||
]; | ||
return \base64_encode(\json_encode($keyData)); | ||
} | ||
|
||
/** | ||
* Retrieve a previously stored private key using the token that was returned | ||
* when the key was stored | ||
* @param string $token the token returned previously by the "storeData" | ||
* method when the key was stored. | ||
* @return PrivateKey the stored private key | ||
*/ | ||
public function retrieveData(string $token): PrivateKey { | ||
$keyData = \json_decode(\base64_decode($token), true); | ||
$privateKey = $this->credentialsManager->retrieve($keyData['userId'], $keyData['rsaId']); | ||
$password = $this->config->getSystemValue('secret', ''); | ||
|
||
return RSA::load($privateKey, $password)->withHash('sha1'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.