diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 9cdd19d7233..6904c8e75a3 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1500,6 +1500,7 @@ return array( 'OC\\Security\\FeaturePolicy\\FeaturePolicy' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', 'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', 'OC\\Security\\Hasher' => $baseDir . '/lib/private/Security/Hasher.php', + 'OC\\Security\\IdentityProof\\Exception\\IdentityProofKeySumException' => $baseDir . '/lib/private/Security/IdentityProof/Exception/IdentityProofKeySumException.php', 'OC\\Security\\IdentityProof\\Key' => $baseDir . '/lib/private/Security/IdentityProof/Key.php', 'OC\\Security\\IdentityProof\\Manager' => $baseDir . '/lib/private/Security/IdentityProof/Manager.php', 'OC\\Security\\IdentityProof\\Signer' => $baseDir . '/lib/private/Security/IdentityProof/Signer.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index fc636f312b5..67b384dc5f3 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1533,6 +1533,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Security\\FeaturePolicy\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', 'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', 'OC\\Security\\Hasher' => __DIR__ . '/../../..' . '/lib/private/Security/Hasher.php', + 'OC\\Security\\IdentityProof\\Exception\\IdentityProofKeySumException' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Exception/IdentityProofKeySumException.php', 'OC\\Security\\IdentityProof\\Key' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Key.php', 'OC\\Security\\IdentityProof\\Manager' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Manager.php', 'OC\\Security\\IdentityProof\\Signer' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Signer.php', diff --git a/lib/private/Security/IdentityProof/Exception/IdentityProofKeySumException.php b/lib/private/Security/IdentityProof/Exception/IdentityProofKeySumException.php new file mode 100644 index 00000000000..cd67a99cc24 --- /dev/null +++ b/lib/private/Security/IdentityProof/Exception/IdentityProofKeySumException.php @@ -0,0 +1,29 @@ + + * + * @author Maxence Lange + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see . + * + */ +namespace OC\Security\IdentityProof\Exception; + +class IdentityProofKeySumException extends \Exception { +} diff --git a/lib/private/Security/IdentityProof/Key.php b/lib/private/Security/IdentityProof/Key.php index 349ffd3c15a..9ea299a4c40 100644 --- a/lib/private/Security/IdentityProof/Key.php +++ b/lib/private/Security/IdentityProof/Key.php @@ -7,6 +7,7 @@ declare(strict_types=1); * * @author Lukas Reschke * @author Roeland Jago Douma + * @author Maxence Lange * * @license GNU AGPL version 3 or any later version * @@ -48,4 +49,8 @@ class Key { public function getPublic(): string { return $this->publicKey; } + + public function getSum(): string { + return hash('sha512', $this->getPublic() . '.' . $this->getPrivate()); + } } diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php index c92d7390969..20d7553c391 100644 --- a/lib/private/Security/IdentityProof/Manager.php +++ b/lib/private/Security/IdentityProof/Manager.php @@ -10,6 +10,7 @@ declare(strict_types=1); * @author Joas Schilling * @author Lukas Reschke * @author Roeland Jago Douma + * @author Maxence Lange * * @license GNU AGPL version 3 or any later version * @@ -27,16 +28,24 @@ declare(strict_types=1); * along with this program. If not, see . * */ + namespace OC\Security\IdentityProof; use OC\Files\AppData\Factory; +use OC\Security\IdentityProof\Exception\IdentityProofKeySumException; use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\IConfig; use OCP\IUser; +use OCP\PreConditionNotMetException; use OCP\Security\ICrypto; use Psr\Log\LoggerInterface; +use RuntimeException; class Manager { + public const SUM_PREFERENCES_KEY = 'identity_proof_key_sum'; + /** @var IAppData */ private $appData; /** @var ICrypto */ @@ -61,7 +70,7 @@ class Manager { * In a separate function for unit testing purposes. * * @return array [$publicKey, $privateKey] - * @throws \RuntimeException + * @throws RuntimeException */ protected function generateKeyPair(): array { $config = [ @@ -74,12 +83,12 @@ class Manager { if ($res === false) { $this->logOpensslError(); - throw new \RuntimeException('OpenSSL reported a problem'); + throw new RuntimeException('OpenSSL reported a problem'); } if (openssl_pkey_export($res, $privateKey, null, $config) === false) { $this->logOpensslError(); - throw new \RuntimeException('OpenSSL reported a problem'); + throw new RuntimeException('OpenSSL reported a problem'); } // Extract the public key from $res to $pubKey @@ -94,8 +103,10 @@ class Manager { * Note: If a key already exists it will be overwritten * * @param string $id key id + * * @return Key - * @throws \RuntimeException + * @throws NotFoundException + * @throws NotPermittedException */ protected function generateKey(string $id): Key { [$publicKey, $privateKey] = $this->generateKeyPair(); @@ -109,54 +120,112 @@ class Manager { $folder->newFile('private') ->putContent($this->crypto->encrypt($privateKey)); $folder->newFile('public') - ->putContent($publicKey); + ->putContent($publicKey); return new Key($publicKey, $privateKey); } /** * Get key for a specific id + * if $userId is set, a checksum of the key will be stored for future comparison * * @param string $id + * @param string $userId + * * @return Key - * @throws \RuntimeException + * @throws NotFoundException + * @throws NotPermittedException + * @throws PreConditionNotMetException */ - protected function retrieveKey(string $id): Key { + protected function retrieveKey(string $id, string $userId = ''): Key { try { $folder = $this->appData->getFolder($id); $privateKey = $this->crypto->decrypt( $folder->getFile('private')->getContent() ); $publicKey = $folder->getFile('public')->getContent(); - return new Key($publicKey, $privateKey); + $key = new Key($publicKey, $privateKey); + + $this->confirmSum($key, $userId); } catch (\Exception $e) { - return $this->generateKey($id); + $key = $this->generateKey($id); + $this->generateKeySum($key, $userId); + } + + return $key; + } + + + /** + * @param Key $key + * @param string $userId + * + * @throws IdentityProofKeySumException + * @throws PreConditionNotMetException + */ + protected function confirmSum(Key $key, string $userId): void { + if ($userId === '') { + $knownSum = $this->config->getAppValue('core', self::SUM_PREFERENCES_KEY, ''); + } else { + $knownSum = $this->config->getUserValue($userId, 'core', self::SUM_PREFERENCES_KEY, ''); + } + + if ($knownSum === '') { // sum is not known, generate a new one + $this->generateKeySum($key, $userId); + } + + if ($knownSum !== $key->getSum()) { + throw new IdentityProofKeySumException(); + } + } + + + /** + * @param Key $key + * @param string $userId + * + * @return void + * @throws PreConditionNotMetException + */ + public function generateKeySum(Key $key, string $userId): void { + if ($userId === '') { + $this->config->setAppValue('core', self::SUM_PREFERENCES_KEY, $key->getSum()); + } else { + $this->config->setUserValue($userId, 'core', self::SUM_PREFERENCES_KEY, $key->getSum()); } } + /** * Get public and private key for $user * * @param IUser $user + * * @return Key - * @throws \RuntimeException + * @throws NotFoundException + * @throws NotPermittedException + * @throws PreConditionNotMetException */ public function getKey(IUser $user): Key { $uid = $user->getUID(); - return $this->retrieveKey('user-' . $uid); + + return $this->retrieveKey('user-' . $uid, $uid); } /** * Get instance wide public and private key * * @return Key - * @throws \RuntimeException + * @throws NotFoundException + * @throws NotPermittedException + * @throws PreConditionNotMetException */ public function getSystemKey(): Key { $instanceId = $this->config->getSystemValue('instanceid', null); if ($instanceId === null) { - throw new \RuntimeException('no instance id!'); + throw new RuntimeException('no instance id!'); } + return $this->retrieveKey('system-' . $instanceId); } diff --git a/tests/lib/Security/IdentityProof/ManagerTest.php b/tests/lib/Security/IdentityProof/ManagerTest.php index 8733d5de8be..847fb3e77f3 100644 --- a/tests/lib/Security/IdentityProof/ManagerTest.php +++ b/tests/lib/Security/IdentityProof/ManagerTest.php @@ -128,10 +128,14 @@ class ManagerTest extends TestCase { $publicFile ); $this->appData - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getFolder') ->with('user-MyUid') ->willReturn($folder); + $this->manager + ->expects($this->once()) + ->method('generateKeyPair') + ->willReturn(['MyPublicKey', 'MyPrivateKey']); $expected = new Key('MyPublicKey', 'MyPrivateKey'); $this->assertEquals($expected, $this->manager->getKey($user));