-
Notifications
You must be signed in to change notification settings - Fork 12
Integrate HSM changes to encryption #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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,119 @@ | ||
| <?php | ||
| /** | ||
| * @author Jörn Friedrich Dreyer <jfd@butonic.de> | ||
| * | ||
| * @copyright Copyright (c) 2017, 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\Encryption\Command; | ||
|
|
||
| use OCA\Encryption\AppInfo\Application; | ||
| use OCA\Encryption\JWT; | ||
| use OCA\Encryption\KeyManager; | ||
| use OCP\AppFramework\Utility\ITimeFactory; | ||
| use OCP\Encryption\IEncryptionModule; | ||
| use OCP\Http\Client\IClient; | ||
| use OCP\Http\Client\IClientService; | ||
| use OCP\IConfig; | ||
| use OCP\ILogger; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Input\InputOption; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| class HSMDaemon extends Command { | ||
|
|
||
| /** @var IClient */ | ||
| private $httpClient; | ||
| /** @var IConfig */ | ||
| private $config; | ||
| /** @var ILogger */ | ||
| private $logger; | ||
| /** @var ITimeFactory */ | ||
| private $timeFactory; | ||
|
|
||
| /** | ||
| * @param IEncryptionModule $encryption | ||
| * @param IClient $httpClient | ||
| * @param IConfig $config | ||
| * @param ILogger $logger | ||
| */ | ||
| public function __construct(IClientService $httpClient, | ||
| IConfig $config, | ||
| ILogger $logger, | ||
| ITimeFactory $timeFactory) { | ||
| $this->httpClient = $httpClient->newClient(); | ||
| $this->config = $config; | ||
| $this->logger = $logger; | ||
| $this->timeFactory = $timeFactory; | ||
| parent::__construct(); | ||
| } | ||
|
|
||
| // TODO add route for hsmdaemon to post current secret | ||
| // TODO add encrypt masterkey command / as option | ||
| // TODO add decrypt option | ||
| protected function configure() { | ||
| $this | ||
| ->setName('encryption:hsmdaemon') | ||
| ->setDescription('hsmdaemon tool'); | ||
| $this->addOption( | ||
| 'export-masterkey', | ||
| null, | ||
| InputOption::VALUE_NONE, | ||
| 'export the private master key in base64' | ||
| ); | ||
| $this->addOption( | ||
| 'import-masterkey', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| 'import a base64 encoded private masterkey' | ||
| ); | ||
| $this->addOption( | ||
| 'decrypt', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| 'decrypt a base64 encoded value with the hsm' | ||
| ); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output) { | ||
| $hsmUrl = $this->config->getAppValue('encryption', 'hsm.url'); | ||
sharidas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (\is_string($hsmUrl) && $hsmUrl !== '') { | ||
| $decrypt = $input->getOption('decrypt'); | ||
| if ($decrypt) { | ||
| $response = $this->httpClient->post($hsmUrl, [ | ||
| 'headers' => [ | ||
| 'Authorization' => 'Bearer ' . JWT::token([ | ||
| 'exp' => $this->timeFactory->getTime() + 120 // 2min for clock skew | ||
| ], 'secret') | ||
| ], | ||
| 'body' => \base64_decode($decrypt) | ||
| ]); | ||
|
|
||
| $output->writeln("received: '".$response->getBody()."'"); | ||
| } elseif ($input->getOption('export-masterkey')) { | ||
| $manager = \OC::$server->getEncryptionKeyStorage(); | ||
| $keyId = $this->config->getAppValue('encryption', 'masterKeyId').'.privateKey'; | ||
| $key = $manager->getSystemUserKey($keyId, \OC::$server->getEncryptionManager()->getDefaultEncryptionModuleId()); | ||
| // FIXME key might be too long to encrypt in one piece | ||
| $output->writeln("current masterkey (base64 encoded): '".\base64_encode($key)."'"); | ||
| } | ||
| } else { | ||
| $output->writeln("<error>hsm.url not set</error>"); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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 hidden or 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,215 @@ | ||
| <?php | ||
| /** | ||
| * @author Björn Schießle <bjoern@schiessle.org> | ||
| * @author Clark Tomlinson <fallen013@gmail.com> | ||
| * @author Joas Schilling <coding@schilljs.com> | ||
| * @author Lukas Reschke <lukas@statuscode.ch> | ||
| * @author Thomas Müller <thomas.mueller@tmit.eu> | ||
| * | ||
| * @copyright Copyright (c) 2017, 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\Encryption\Crypto; | ||
|
|
||
| use GuzzleHttp\Exception\ServerException; | ||
| use OC\Encryption\Exceptions\DecryptionFailedException; | ||
| use OCA\Encryption\Exceptions\MultiKeyDecryptException; | ||
| use OCA\Encryption\Exceptions\MultiKeyEncryptException; | ||
| use OCA\Encryption\JWT; | ||
| use OCP\AppFramework\Utility\ITimeFactory; | ||
| use OCP\Http\Client\IClientService; | ||
| use OCP\IConfig; | ||
| use OCP\IL10N; | ||
| use OCP\ILogger; | ||
| use OCP\IRequest; | ||
| use OCP\IUserSession; | ||
|
|
||
| /** | ||
| * Class CryptHSM moves the key generation and multiKeyDecrytion to an HSM. | ||
| * multiKeyEncrypt can be done locally because we have the public key | ||
| * | ||
| * @package OCA\Encryption\Crypto | ||
| */ | ||
| class CryptHSM extends Crypt { | ||
|
|
||
| /** | ||
| * @var IClientService | ||
| */ | ||
| protected $clientService; | ||
|
|
||
| /** | ||
| * @var string | ||
| */ | ||
| private $hsmUrl; | ||
|
|
||
| /** | ||
| * @var int | ||
| */ | ||
| private $clockSkew; | ||
|
|
||
| /** | ||
| * @var | ||
| */ | ||
| private $secret; | ||
|
|
||
| /** | ||
| * @var IRequest | ||
| */ | ||
| private $request; | ||
|
|
||
| /** | ||
| * ITimeFactory | ||
| */ | ||
| private $timeFactory; | ||
|
|
||
| const PATH_NEW_KEY = '/keys/new'; | ||
| const PATH_DECRYPT = '/decrypt/'; // appended with keyid | ||
|
|
||
| /** | ||
| * @param ILogger $logger | ||
| * @param IUserSession $userSession | ||
| * @param IConfig $config | ||
| * @param IL10N $l | ||
| * @param IClientService $clientService | ||
| * @param ITimeFactory $timeFactory | ||
| */ | ||
| public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l, IClientService $clientService, IRequest $request, ITimeFactory $timeFactory) { | ||
| parent::__construct($logger, $userSession, $config, $l); | ||
| $this->hsmUrl = \rtrim($this->config->getAppValue('encryption', 'hsm.url'), '/'); // no default, because Application DI only instantiates this if it is configured non empty | ||
| $this->secret = $this->config->getAppValue('encryption', 'hsm.jwt.secret', 'secret'); | ||
| $this->clockSkew = (int)$this->config->getAppValue('encryption', 'hsm.jwt.clockskew', 120); // 2min | ||
| $this->clientService = $clientService; | ||
| $this->request = $request; | ||
| $this->timeFactory = $timeFactory; | ||
| } | ||
|
|
||
| /** | ||
| * create new private/public key-pair for user | ||
| * any key config happens in the service | ||
| * | ||
| * @param $label string human readable name | ||
| * @return array|bool | ||
| */ | ||
| public function createKeyPair($label = null) { | ||
| $response = $this->clientService->newClient()->post( | ||
| $this->hsmUrl.$this::PATH_NEW_KEY, [ | ||
| 'headers' => [ | ||
| 'Authorization' => 'Bearer ' . JWT::token([ | ||
| 'iss' => $this->config->getSystemValue('instanceid'), | ||
| 'sub' => $label, | ||
| 'aud' => 'hsmdaemon', | ||
| 'exp' => $this->timeFactory->getTime() + $this->clockSkew, | ||
| 'rid' => $this->request->getId(), | ||
| ], $this->secret) | ||
| ], | ||
| ]); | ||
| $keyPair = \json_decode($response->getBody(), true); | ||
|
|
||
| return [ | ||
| 'publicKey' => $keyPair['publicKey'], | ||
| 'privateKey' => $keyPair['privateKeyId'] // returns the key id in the hsm, not the actual private key | ||
| ]; | ||
| } | ||
| /** | ||
| * check if it is a valid private key | ||
| * | ||
| * @param string $plainKey | ||
| * @return bool | ||
| */ | ||
| protected function isValidPrivateKey($plainKey) { // unneded for HSM, may check if it is '*secret*'? | ||
| // TODO check if it is a uuid? | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * @param $encKeyFile | ||
| * @param $shareKey | ||
| * @param $privateKey string contains the key uuid in the hsm | ||
| * @return string | ||
| * @throws MultiKeyDecryptException | ||
| */ | ||
| public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) { // done with HSM, private key contains the key id in the hsm | ||
| if (!$encKeyFile) { | ||
| throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content'); | ||
| } | ||
|
|
||
| // decrypt the shareKey | ||
| $keyId = $privateKey; // TODO check $privateKey is a uuid, should have been generated with genkey | ||
|
|
||
| try { | ||
| $response = $this->clientService->newClient()->post( | ||
| $this->hsmUrl.$this::PATH_DECRYPT.$keyId, [ | ||
| 'headers' => [ | ||
| 'Authorization' => 'Bearer ' . JWT::token([ | ||
| 'iss' => $this->config->getSystemValue('instanceid'), | ||
| // 'sub' => $keyId, does not add anything right now, use md5 of $shareKey? | ||
| 'aud' => 'hsmdaemon', | ||
| 'exp' => $this->timeFactory->getTime() + $this->clockSkew, | ||
| 'rid' => $this->request->getId(), | ||
| ], $this->secret) | ||
| ], | ||
| 'body' => $shareKey | ||
| ]); | ||
| $decryptedKey = $response->getBody(); | ||
|
|
||
| // now decode the file. | ||
| // version and position are 0 because we always use fresh random data as passphrase | ||
| $decryptedContent = $this->symmetricDecryptFileContent($encKeyFile, $decryptedKey, self::DEFAULT_CIPHER, 0, 0); | ||
|
|
||
| return $decryptedContent; | ||
| } catch (ServerException $e) { | ||
| $body = $e->getResponse()->getBody(); | ||
| $this->logger->logException($e, ['message' => $body, 'app' => __CLASS__]); | ||
| throw new MultiKeyDecryptException('Cannot multikey decrypt with HSM', '', 0, $e); | ||
| } catch (DecryptionFailedException $e) { | ||
| throw new MultiKeyDecryptException('Cannot multikey decrypt', '', 0, $e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param string $plainContent | ||
| * @param array $keyFiles | ||
| * @return array | ||
| * @throws MultiKeyEncryptException | ||
| */ | ||
| public function multiKeyEncrypt($plainContent, array $keyFiles) { // done with HSM, needs to return the key ids from the hsm | ||
| $randomKey = $this->generateFileKey(); | ||
|
|
||
| // encrypt $plainContent using a random key and iv. | ||
| // version and position are 0 because we use fresh random data as passphrase | ||
| $sealedContent = $this->symmetricEncryptFileContent($plainContent, $randomKey, 0, 0); | ||
|
|
||
| if ($sealedContent === false) { | ||
| throw new MultiKeyEncryptException('Could not create sealed content'); | ||
| } | ||
|
|
||
| $encryptedKeys = []; | ||
| // encrypt $randomKey with all public keys | ||
| foreach ($keyFiles as $userId => $publicKey) { | ||
| // FIXME use OPENSSL_PKCS1_OAEP_PADDING, implemented in opensc on 2017-10-19 with https://github.com/OpenSC/OpenSC/pull/1169, see http://php.net/manual/de/function.openssl-public-encrypt.php#118466 | ||
| // TODO make padding configurable? | ||
| // TODO add command to hsmdaemon to see supported paddings? | ||
| \openssl_public_encrypt($randomKey, $encryptedKey, $publicKey, OPENSSL_PKCS1_PADDING); | ||
| $encryptedKeys[$userId] = $encryptedKey; | ||
| } | ||
|
|
||
| return [ | ||
| 'keys' => $encryptedKeys, | ||
| 'data' => $sealedContent | ||
| ]; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.