diff options
author | Joachim Bauch <bauch@struktur.de> | 2022-06-10 09:33:14 +0200 |
---|---|---|
committer | Joachim Bauch <bauch@struktur.de> | 2022-07-07 12:00:29 +0200 |
commit | 79c1920a23147d86aa06aa10789e2d2c40dd54d9 (patch) | |
tree | 2e92d6d955746cfbac1e1f74a4cf1b4080cacb26 /lib/Config.php | |
parent | 076db311466bd60f4d4c8f58632aa7de0a3dd601 (diff) |
Implement JWT auth for signaling connection.
Signed-off-by: Joachim Bauch <bauch@struktur.de>
Diffstat (limited to 'lib/Config.php')
-rw-r--r-- | lib/Config.php | 116 |
1 files changed, 115 insertions, 1 deletions
diff --git a/lib/Config.php b/lib/Config.php index 1628aa1cd..c691fdb20 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -23,12 +23,16 @@ declare(strict_types=1); namespace OCA\Talk; +use Firebase\JWT\JWT; + use OCP\AppFramework\Utility\ITimeFactory; use OCA\Talk\Events\GetTurnServersEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IGroupManager; +use OCP\IURLGenerator; use OCP\IUser; +use OCP\IUserManager; use OCP\Security\ISecureRandom; class Config { @@ -36,9 +40,14 @@ class Config { public const SIGNALING_EXTERNAL = 'external'; public const SIGNALING_CLUSTER_CONVERSATION = 'conversation_cluster'; + public const SIGNALING_TICKET_V1 = 1; + public const SIGNALING_TICKET_V2 = 2; + protected IConfig $config; protected ITimeFactory $timeFactory; private IGroupManager $groupManager; + private IUserManager $userManager; + private IURLGenerator $urlGenerator; private ISecureRandom $secureRandom; private IEventDispatcher $dispatcher; @@ -47,11 +56,15 @@ class Config { public function __construct(IConfig $config, ISecureRandom $secureRandom, IGroupManager $groupManager, + IUserManager $userManager, + IURLGenerator $urlGenerator, ITimeFactory $timeFactory, IEventDispatcher $dispatcher) { $this->config = $config; $this->secureRandom = $secureRandom; $this->groupManager = $groupManager; + $this->userManager = $userManager; + $this->urlGenerator = $urlGenerator; $this->timeFactory = $timeFactory; $this->dispatcher = $dispatcher; } @@ -340,10 +353,26 @@ class Config { } /** + * @param int $version * @param string $userId * @return string */ - public function getSignalingTicket(?string $userId): string { + public function getSignalingTicket(int $version, ?string $userId): string { + switch ($version) { + case self::SIGNALING_TICKET_V1: + return $this->getSignalingTicketV1($userId); + case self::SIGNALING_TICKET_V2: + return $this->getSignalingTicketV2($userId); + default: + return $this->getSignalingTicketV1($userId); + } + } + + /** + * @param string $userId + * @return string + */ + private function getSignalingTicketV1(?string $userId): string { if (empty($userId)) { $secret = $this->config->getAppValue('spreed', 'signaling_ticket_secret'); } else { @@ -369,6 +398,91 @@ class Config { return $data . ':' . $hash; } + private function ensureSignalingTokenKeys(string $alg): void { + $secret = $this->config->getAppValue('spreed', 'signaling_token_privkey_' . strtolower($alg)); + if ($secret) { + return; + } + + if (substr($alg, 0, 2) === 'EC') { + $privKey = openssl_pkey_new([ + 'curve_name' => 'prime256v1', + 'private_key_type' => OPENSSL_KEYTYPE_EC, + ]); + } elseif (substr($alg, 0, 2) === 'RS') { + $privKey = openssl_pkey_new([ + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + } else { + throw new \Exception('Unsupported algorithm ' . $alg); + } + $pubKey = openssl_pkey_get_details($privKey); + $public = $pubKey['key']; + + if (!openssl_pkey_export($privKey, $secret)) { + throw new \Exception('Could not export private key'); + } + + $this->config->setAppValue('spreed', 'signaling_token_privkey_' . strtolower($alg), $secret); + $this->config->setAppValue('spreed', 'signaling_token_pubkey_' . strtolower($alg), $public); + } + + public function getSignalingTokenAlgorithm(): string { + return $this->config->getAppValue('spreed', 'signaling_token_alg', 'ES256'); + } + + public function getSignalingTokenPrivateKey(?string $alg = null): string { + if (!$alg) { + $alg = $this->getSignalingTokenAlgorithm(); + } + $this->ensureSignalingTokenKeys($alg); + + return $this->config->getAppValue('spreed', 'signaling_token_privkey_' . strtolower($alg)); + } + + public function getSignalingTokenPublicKey(?string $alg = null): string { + if (!$alg) { + $alg = $this->getSignalingTokenAlgorithm(); + } + $this->ensureSignalingTokenKeys($alg); + + return $this->config->getAppValue('spreed', 'signaling_token_pubkey_' . strtolower($alg)); + } + + /** + * @param IUser $user + * @return array + */ + public function getSignalingUserData(IUser $user): array { + return [ + 'displayname' => $user->getDisplayName(), + ]; + } + + /** + * @param string $userId + * @return string + */ + private function getSignalingTicketV2(?string $userId): string { + $timestamp = $this->timeFactory->getTime(); + $data = [ + 'iss' => $this->urlGenerator->getAbsoluteURL(''), + 'iat' => $timestamp, + 'exp' => $timestamp + 60, // Valid for 1 minute. + ]; + $user = !empty($userId) ? $this->userManager->get($userId) : null; + if ($user instanceof IUser) { + $data['sub'] = $user->getUID(); + $data['userdata'] = $this->getSignalingUserData($user); + } + + $alg = $this->getSignalingTokenAlgorithm(); + $secret = $this->getSignalingTokenPrivateKey($alg); + $token = JWT::encode($data, $secret, $alg); + return $token; + } + /** * @param string $userId * @param string $ticket |