Skip to content

Commit

Permalink
Upgrade shared-secret-authenticator to v2
Browse files Browse the repository at this point in the history
Works well, but is still affected by element-hq/element-web#19605
  • Loading branch information
spantaleev committed Nov 15, 2021
1 parent d03f20a commit 5455502
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 46 deletions.
34 changes: 27 additions & 7 deletions etc/services/matrix-synapse/homeserver.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# vim:ft=yaml
# Configuration file for Synapse.
#
# This is a YAML file: see [1] for a quick introduction. Note in particular
# that *indentation is important*: all the elements of a list or dictionary
# should have the same indentation.
#
# [1] https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html


## Modules ##

# Server admins can expand Synapse's functionality with external modules.
#
# See https://matrix-org.github.io/synapse/develop/modules.html for more
# documentation on how to configure or create custom modules for Synapse.
#
modules:
- module: shared_secret_authenticator.SharedSecretAuthProvider
config:
shared_secret: "7DXvheK1400ydCHjAymDU50FkeUedQJ2AYpitr3inLpSBIdRJN4kfS5IkGYvUptF"
m_login_password_support_enabled: true
## Server ##

# The domain name of the server, with optional explicit port.
Expand Down Expand Up @@ -1200,13 +1220,13 @@ password_config:
# #bind_dn:
# #bind_password:
# #filter: "(objectClass=posixAccount)"
password_providers:
- module: "shared_secret_authenticator.SharedSecretAuthenticator"
config:
sharedSecret: "7DXvheK1400ydCHjAymDU50FkeUedQJ2AYpitr3inLpSBIdRJN4kfS5IkGYvUptF"
- module: "rest_auth_provider.RestAuthProvider"
config:
endpoint: "http://matrix-corporal:41080/_matrix/corporal"
# password_providers:
# - module: "shared_secret_authenticator.SharedSecretAuthenticator"
# config:
# sharedSecret: "7DXvheK1400ydCHjAymDU50FkeUedQJ2AYpitr3inLpSBIdRJN4kfS5IkGYvUptF"
# - module: "rest_auth_provider.RestAuthProvider"
# config:
# endpoint: "http://matrix-corporal:41080/_matrix/corporal"


# Clients requesting push notifications can either have the body of
Expand Down
125 changes: 86 additions & 39 deletions etc/services/shared_secret_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Shared Secret Authenticator module for Matrix Synapse
# Copyright (C) 2018 Slavi Pantaleev
#
# http://devture.com/
# https://devture.com/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
Expand All @@ -18,53 +18,100 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
from typing import Awaitable, Callable, Optional, Tuple

import logging
import hashlib
import hmac
from twisted.internet import defer
import logging

import synapse
from synapse import module_api

logger = logging.getLogger(__name__)

class SharedSecretAuthenticator(object):
class SharedSecretAuthProvider:
def __init__(self, config: dict, api: module_api):
for k in ('shared_secret',):
if k not in config:
raise KeyError('Required `{0}` configuration key not found'.format(k))

m_login_password_support_enabled = bool(config['m_login_password_support_enabled']) if 'm_login_password_support_enabled' in config else False

self.api = api
self.shared_secret = config['shared_secret']

auth_checkers: Optional[Dict[Tuple[str, Tuple], CHECK_AUTH_CALLBACK]] = {}
auth_checkers[("com.devture.shared_secret_auth", ("token",))] = self.check_com_devture_shared_secret_auth
if m_login_password_support_enabled:
auth_checkers[("m.login.password", ("password",))] = self.check_m_login_password

enabled_login_types = [k[0] for k in auth_checkers]
logger.info('Enabled login types: %s', enabled_login_types)

def __init__(self, config, account_handler):
self.account_handler = account_handler
self.sharedSecret = config['sharedSecret']
api.register_password_auth_provider_callbacks(
auth_checkers=auth_checkers,
)

@defer.inlineCallbacks
def check_password(self, user_id, password):
# The password is supposed to be an HMAC of the user id, keyed with the shared secret.
# It's not really a password in this case.
given_hmac = password.encode('utf-8')
async def check_com_devture_shared_secret_auth(
self,
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if login_type != "com.devture.shared_secret_auth":
return None
return await self._log_in_username_with_token("com.devture.shared_secret_auth", username, login_dict.get("token"))

logger.info('Authenticating user: %s', user_id)
async def check_m_login_password(
self,
username: str,
login_type: str,
login_dict: "synapse.module_api.JsonDict",
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
if login_type != "m.login.password":
return None
return await self._log_in_username_with_token("m.login.password", username, login_dict.get("password"))

h = hmac.new(self.sharedSecret.encode('utf-8'), user_id.encode('utf-8'), hashlib.sha512)
async def _log_in_username_with_token(
self,
login_type: str,
username: str,
token: str,
) -> Optional[
Tuple[
str,
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
]
]:
logger.info('Authenticating user `%s` with login type `%s`', username, login_type)

full_user_id = self.api.get_qualified_user_id(username)

# The password (token) is supposed to be an HMAC of the full user id, keyed with the shared secret.
given_hmac = token.encode('utf-8')

h = hmac.new(self.shared_secret.encode('utf-8'), full_user_id.encode('utf-8'), hashlib.sha512)
computed_hmac = h.hexdigest().encode('utf-8')

try:
is_identical = hmac.compare_digest(computed_hmac, given_hmac)
except AttributeError:
# `hmac.compare_digest` is only available on Python >= 2.7.7
# Fall back to being somewhat insecure on older versions.
is_identical = (computed_hmac == given_hmac)

if not is_identical:
logger.info('Bad hmac value for user: %s', user_id)
defer.returnValue(False)
return

if not (yield self.account_handler.check_user_exists(user_id)):
logger.info('Refusing to authenticate missing user: %s', user_id)
defer.returnValue(False)
return

logger.info('Authenticated user: %s', user_id)
defer.returnValue(True)

@staticmethod
def parse_config(config):
if 'sharedSecret' not in config:
raise Exception('Missing sharedSecret parameter for SharedSecretAuthenticator')
return config
if not hmac.compare_digest(computed_hmac, given_hmac):
logger.info('Bad hmac value for user: %s', full_user_id)
return None

user_info = await self.api.get_userinfo_by_id(full_user_id)
if user_info is None:
logger.info('Refusing to authenticate missing user: %s', full_user_id)
return None

logger.info('Authenticated user: %s', full_user_id)

return full_user_id, None

0 comments on commit 5455502

Please sign in to comment.