From 5b3e9713dd098df95b321f216105b2298deaeb92 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Thu, 1 Oct 2015 17:49:52 +0100 Subject: Implement third party identifier invites --- synapse/api/auth.py | 33 ++++++++++++++++++++++++++++++++- synapse/api/constants.py | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 847ff60671..37f7f1bf79 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -14,15 +14,19 @@ # limitations under the License. """This module contains classes for authenticating the user.""" +from nacl.exceptions import BadSignatureError from twisted.internet import defer from synapse.api.constants import EventTypes, Membership, JoinRules from synapse.api.errors import AuthError, Codes, SynapseError from synapse.util.logutils import log_function +from synapse.util.thirdpartyinvites import ThirdPartyInvites from synapse.types import UserID, EventID +from unpaddedbase64 import decode_base64 import logging +import nacl.signing import pymacaroons logger = logging.getLogger(__name__) @@ -31,6 +35,7 @@ logger = logging.getLogger(__name__) AuthEventTypes = ( EventTypes.Create, EventTypes.Member, EventTypes.PowerLevels, EventTypes.JoinRules, EventTypes.RoomHistoryVisibility, + EventTypes.ThirdPartyInvite, ) @@ -318,7 +323,8 @@ class Auth(object): pass elif join_rule == JoinRules.INVITE: if not caller_in_room and not caller_invited: - raise AuthError(403, "You are not invited to this room.") + if not self._verify_third_party_invite(event, auth_events): + raise AuthError(403, "You are not invited to this room.") else: # TODO (erikj): may_join list # TODO (erikj): private rooms @@ -344,6 +350,31 @@ class Auth(object): return True + def _verify_third_party_invite(self, event, auth_events): + for key in ThirdPartyInvites.JOIN_KEYS: + if key not in event.content: + return False + token = event.content["token"] + invite_event = auth_events.get( + (EventTypes.ThirdPartyInvite, token,) + ) + if not invite_event: + return False + try: + public_key = event.content["public_key"] + key_validity_url = event.content["key_validity_url"] + if invite_event.content["public_key"] != public_key: + return False + if invite_event.content["key_validity_url"] != key_validity_url: + return False + verify_key = nacl.signing.VerifyKey(decode_base64(public_key)) + encoded_signature = event.content["signature"] + signature = decode_base64(encoded_signature) + verify_key.verify(token, signature) + return True + except (KeyError, BadSignatureError,): + return False + def _get_power_level_event(self, auth_events): key = (EventTypes.PowerLevels, "", ) return auth_events.get(key) diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 3385664394..bfc230d126 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -63,6 +63,7 @@ class EventTypes(object): PowerLevels = "m.room.power_levels" Aliases = "m.room.aliases" Redaction = "m.room.redaction" + ThirdPartyInvite = "m.room.third_party_invite" RoomHistoryVisibility = "m.room.history_visibility" CanonicalAlias = "m.room.canonical_alias" -- cgit 1.5.1 From 1cacc71050a44f00ddf2226f907e9b794672671d Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 6 Oct 2015 10:13:28 -0500 Subject: Add third party invites to auth_events for joins --- synapse/api/auth.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 6607d08488..adb9a776e0 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -677,6 +677,11 @@ class Auth(object): if e_type == Membership.JOIN: if member_event and not is_public: auth_ids.append(member_event.event_id) + if ThirdPartyInvites.has_join_keys(event.content): + key = (EventTypes.ThirdPartyInvite, event.content["token"]) + invite = current_state.get(key) + if invite: + auth_ids.append(invite.event_id) else: if member_event: auth_ids.append(member_event.event_id) -- cgit 1.5.1 From 17dffef5ec74d789f68096c95d29cdcad57ce5c7 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 13 Oct 2015 15:48:12 +0100 Subject: Move event contents into third_party_layout field --- synapse/api/auth.py | 21 ++++++++++++--------- synapse/federation/federation_client.py | 4 ++-- synapse/handlers/_base.py | 2 +- synapse/handlers/federation.py | 5 +++-- synapse/handlers/room.py | 11 +++++++---- synapse/rest/client/v1/room.py | 3 ++- synapse/util/thirdpartyinvites.py | 10 ++++++++-- 7 files changed, 35 insertions(+), 21 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index adb9a776e0..ca280707c5 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -374,24 +374,24 @@ class Auth(object): return True def _verify_third_party_invite(self, event, auth_events): - for key in ThirdPartyInvites.JOIN_KEYS: - if key not in event.content: - return False - token = event.content["token"] + if not ThirdPartyInvites.join_has_third_party_invite(event.content): + return False + join_third_party_invite = event.content["third_party_invite"] + token = join_third_party_invite["token"] invite_event = auth_events.get( (EventTypes.ThirdPartyInvite, token,) ) if not invite_event: return False try: - public_key = event.content["public_key"] - key_validity_url = event.content["key_validity_url"] + public_key = join_third_party_invite["public_key"] + key_validity_url = join_third_party_invite["key_validity_url"] if invite_event.content["public_key"] != public_key: return False if invite_event.content["key_validity_url"] != key_validity_url: return False verify_key = nacl.signing.VerifyKey(decode_base64(public_key)) - encoded_signature = event.content["signature"] + encoded_signature = join_third_party_invite["signature"] signature = decode_base64(encoded_signature) verify_key.verify(token, signature) return True @@ -677,8 +677,11 @@ class Auth(object): if e_type == Membership.JOIN: if member_event and not is_public: auth_ids.append(member_event.event_id) - if ThirdPartyInvites.has_join_keys(event.content): - key = (EventTypes.ThirdPartyInvite, event.content["token"]) + if ThirdPartyInvites.join_has_third_party_invite(event.content): + key = ( + EventTypes.ThirdPartyInvite, + event.content["third_party_invite"]["token"] + ) invite = current_state.get(key) if invite: auth_ids.append(invite.event_id) diff --git a/synapse/federation/federation_client.py b/synapse/federation/federation_client.py index 06b0c7adcf..6be83d82e7 100644 --- a/synapse/federation/federation_client.py +++ b/synapse/federation/federation_client.py @@ -363,8 +363,8 @@ class FederationClient(FederationBase): continue args = {} - if ThirdPartyInvites.has_join_keys(content): - ThirdPartyInvites.copy_join_keys(content, args) + if ThirdPartyInvites.join_has_third_party_invite(content): + ThirdPartyInvites.copy_join_keys(content["third_party_invite"], args) try: ret = yield self.transport_layer.make_join( destination, room_id, user_id, args diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py index faf99f5bd3..4165c56bed 100644 --- a/synapse/handlers/_base.py +++ b/synapse/handlers/_base.py @@ -127,7 +127,7 @@ class BaseHandler(object): if ( event.type == EventTypes.Member and event.content["membership"] == Membership.JOIN and - ThirdPartyInvites.has_join_keys(event.content) + ThirdPartyInvites.join_has_third_party_invite(event.content) ): yield ThirdPartyInvites.check_key_valid( self.hs.get_simple_http_client(), diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py index 8197d8b2d0..8606c0d285 100644 --- a/synapse/handlers/federation.py +++ b/synapse/handlers/federation.py @@ -705,7 +705,8 @@ class FederationHandler(BaseHandler): """ event_content = {"membership": Membership.JOIN} if ThirdPartyInvites.has_join_keys(query): - ThirdPartyInvites.copy_join_keys(query, event_content) + event_content["third_party_invite"] = {} + ThirdPartyInvites.copy_join_keys(query, event_content["third_party_invite"]) builder = self.event_builder_factory.new({ "type": EventTypes.Member, @@ -721,7 +722,7 @@ class FederationHandler(BaseHandler): self.auth.check(event, auth_events=context.current_state) - if ThirdPartyInvites.has_join_keys(event.content): + if ThirdPartyInvites.join_has_third_party_invite(event.content): ThirdPartyInvites.check_key_valid(self.hs.get_simple_http_client(), event) defer.returnValue(event) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index b856b424a7..e07472b4b9 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -483,10 +483,13 @@ class RoomMemberHandler(BaseHandler): should_do_dance = not self.hs.is_mine(inviter) room_hosts = [inviter.domain] - elif "sender" in event.content: - inviter = UserID.from_string(event.content["sender"]) - should_do_dance = not self.hs.is_mine(inviter) - room_hosts = [inviter.domain] + elif "third_party_invite" in event.content: + if "sender" in event.content["third_party_invite"]: + inviter = UserID.from_string( + event.content["third_party_invite"]["sender"] + ) + should_do_dance = not self.hs.is_mine(inviter) + room_hosts = [inviter.domain] else: # return the same error as join_room_alias does raise SynapseError(404, "No known servers") diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py index ff84affea3..1cb6ba4f1f 100644 --- a/synapse/rest/client/v1/room.py +++ b/synapse/rest/client/v1/room.py @@ -456,7 +456,8 @@ class RoomMembershipRestServlet(ClientV1RestServlet): } if membership_action == "join" and ThirdPartyInvites.has_join_keys(content): - ThirdPartyInvites.copy_join_keys(content, event_content) + event_content["third_party_invite"] = {} + ThirdPartyInvites.copy_join_keys(content, event_content["third_party_invite"]) yield msg_handler.create_and_send_event( { diff --git a/synapse/util/thirdpartyinvites.py b/synapse/util/thirdpartyinvites.py index c30279de67..ad0f4e88e9 100644 --- a/synapse/util/thirdpartyinvites.py +++ b/synapse/util/thirdpartyinvites.py @@ -42,6 +42,12 @@ class ThirdPartyInvites(object): return False return True + @classmethod + def join_has_third_party_invite(cls, content): + if "third_party_invite" not in content: + return False + return cls.has_join_keys(content["third_party_invite"]) + @classmethod def copy_join_keys(cls, src, dst): for key in cls.JOIN_KEYS: @@ -53,8 +59,8 @@ class ThirdPartyInvites(object): def check_key_valid(cls, http_client, event): try: response = yield http_client.get_json( - event.content["key_validity_url"], - {"public_key": event.content["public_key"]} + event.content["third_party_invite"]["key_validity_url"], + {"public_key": event.content["third_party_invite"]["public_key"]} ) if not response["valid"]: raise AuthError(403, "Third party certificate was invalid") -- cgit 1.5.1 From 95e53ac53576de2980e8f7683b59f4fa7fce038b Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 13 Oct 2015 17:18:24 +0100 Subject: Add some docstring --- synapse/api/auth.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index ca280707c5..c0762df567 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -374,6 +374,21 @@ class Auth(object): return True def _verify_third_party_invite(self, event, auth_events): + """ + Validates that the join event is authorized by a previous third-party invite. + + Checks that the public key, and keyserver, match those in the invite, + and that the join event has a signature issued using that public key. + + Args: + event: The m.room.member join event being validated. + auth_events: All relevant previous context events which may be used + for authorization decisions. + + Return: + True if the event fulfills the expectations of a previous third party + invite event. + """ if not ThirdPartyInvites.join_has_third_party_invite(event.content): return False join_third_party_invite = event.content["third_party_invite"] -- cgit 1.5.1 From 0c38e8637ff549a21c763f02f52306b5c729d26b Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Tue, 13 Oct 2015 18:00:38 +0100 Subject: Remove unnecessary class-wrapping --- synapse/api/auth.py | 6 +-- synapse/federation/federation_client.py | 6 +-- synapse/federation/federation_server.py | 6 +-- synapse/handlers/_base.py | 6 +-- synapse/handlers/federation.py | 13 ++++--- synapse/rest/client/v1/room.py | 11 +++--- synapse/util/third_party_invites.py | 69 +++++++++++++++++++++++++++++++++ synapse/util/thirdpartyinvites.py | 68 -------------------------------- 8 files changed, 94 insertions(+), 91 deletions(-) create mode 100644 synapse/util/third_party_invites.py delete mode 100644 synapse/util/thirdpartyinvites.py (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index c0762df567..e96d747b99 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -22,7 +22,7 @@ from synapse.api.constants import EventTypes, Membership, JoinRules from synapse.api.errors import AuthError, Codes, SynapseError from synapse.types import RoomID, UserID, EventID from synapse.util.logutils import log_function -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites from unpaddedbase64 import decode_base64 import logging @@ -389,7 +389,7 @@ class Auth(object): True if the event fulfills the expectations of a previous third party invite event. """ - if not ThirdPartyInvites.join_has_third_party_invite(event.content): + if not third_party_invites.join_has_third_party_invite(event.content): return False join_third_party_invite = event.content["third_party_invite"] token = join_third_party_invite["token"] @@ -692,7 +692,7 @@ class Auth(object): if e_type == Membership.JOIN: if member_event and not is_public: auth_ids.append(member_event.event_id) - if ThirdPartyInvites.join_has_third_party_invite(event.content): + if third_party_invites.join_has_third_party_invite(event.content): key = ( EventTypes.ThirdPartyInvite, event.content["third_party_invite"]["token"] diff --git a/synapse/federation/federation_client.py b/synapse/federation/federation_client.py index 6be83d82e7..d974e920c3 100644 --- a/synapse/federation/federation_client.py +++ b/synapse/federation/federation_client.py @@ -25,7 +25,7 @@ from synapse.api.errors import ( from synapse.util import unwrapFirstError from synapse.util.caches.expiringcache import ExpiringCache from synapse.util.logutils import log_function -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites from synapse.events import FrozenEvent import synapse.metrics @@ -363,8 +363,8 @@ class FederationClient(FederationBase): continue args = {} - if ThirdPartyInvites.join_has_third_party_invite(content): - ThirdPartyInvites.copy_join_keys(content["third_party_invite"], args) + if third_party_invites.join_has_third_party_invite(content): + args = third_party_invites.extract_join_keys(content) try: ret = yield self.transport_layer.make_join( destination, room_id, user_id, args diff --git a/synapse/federation/federation_server.py b/synapse/federation/federation_server.py index d71ab44271..7934f740e0 100644 --- a/synapse/federation/federation_server.py +++ b/synapse/federation/federation_server.py @@ -27,7 +27,7 @@ from synapse.api.errors import FederationError, SynapseError, Codes from synapse.crypto.event_signing import compute_event_signature -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites import simplejson as json import logging @@ -232,8 +232,8 @@ class FederationServer(FederationBase): @defer.inlineCallbacks def on_make_join_request(self, room_id, user_id, query): threepid_details = {} - if ThirdPartyInvites.has_join_keys(query): - for k in ThirdPartyInvites.JOIN_KEYS: + if third_party_invites.has_join_keys(query): + for k in third_party_invites.JOIN_KEYS: if not isinstance(query[k], list) or len(query[k]) != 1: raise FederationError( "FATAL", diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py index 4165c56bed..97edec6ec6 100644 --- a/synapse/handlers/_base.py +++ b/synapse/handlers/_base.py @@ -21,7 +21,7 @@ from synapse.api.constants import Membership, EventTypes from synapse.types import UserID, RoomAlias from synapse.util.logcontext import PreserveLoggingContext -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites import logging @@ -127,9 +127,9 @@ class BaseHandler(object): if ( event.type == EventTypes.Member and event.content["membership"] == Membership.JOIN and - ThirdPartyInvites.join_has_third_party_invite(event.content) + third_party_invites.join_has_third_party_invite(event.content) ): - yield ThirdPartyInvites.check_key_valid( + yield third_party_invites.check_key_valid( self.hs.get_simple_http_client(), event ) diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py index f10e5192e2..2b3c4cec8e 100644 --- a/synapse/handlers/federation.py +++ b/synapse/handlers/federation.py @@ -39,7 +39,7 @@ from twisted.internet import defer import itertools import logging -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites logger = logging.getLogger(__name__) @@ -704,9 +704,10 @@ class FederationHandler(BaseHandler): process it until the other server has signed it and sent it back. """ event_content = {"membership": Membership.JOIN} - if ThirdPartyInvites.has_join_keys(query): - event_content["third_party_invite"] = {} - ThirdPartyInvites.copy_join_keys(query, event_content["third_party_invite"]) + if third_party_invites.has_join_keys(query): + event_content["third_party_invite"] = ( + third_party_invites.extract_join_keys(query) + ) builder = self.event_builder_factory.new({ "type": EventTypes.Member, @@ -722,8 +723,8 @@ class FederationHandler(BaseHandler): self.auth.check(event, auth_events=context.current_state) - if ThirdPartyInvites.join_has_third_party_invite(event.content): - ThirdPartyInvites.check_key_valid(self.hs.get_simple_http_client(), event) + if third_party_invites.join_has_third_party_invite(event.content): + third_party_invites.check_key_valid(self.hs.get_simple_http_client(), event) defer.returnValue(event) diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py index 1aca203744..1f45fcc6f1 100644 --- a/synapse/rest/client/v1/room.py +++ b/synapse/rest/client/v1/room.py @@ -26,7 +26,7 @@ from synapse.events.utils import serialize_event import simplejson as json import logging import urllib -from synapse.util.thirdpartyinvites import ThirdPartyInvites +from synapse.util import third_party_invites logger = logging.getLogger(__name__) @@ -415,7 +415,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet): # target user is you unless it is an invite state_key = user.to_string() - if membership_action == "invite" and ThirdPartyInvites.has_invite_keys(content): + if membership_action == "invite" and third_party_invites.has_invite_keys(content): yield self.handlers.room_member_handler.do_3pid_invite( room_id, user, @@ -446,9 +446,10 @@ class RoomMembershipRestServlet(ClientV1RestServlet): "membership": unicode(membership_action), } - if membership_action == "join" and ThirdPartyInvites.has_join_keys(content): - event_content["third_party_invite"] = {} - ThirdPartyInvites.copy_join_keys(content, event_content["third_party_invite"]) + if membership_action == "join" and third_party_invites.has_join_keys(content): + event_content["third_party_invite"] = ( + third_party_invites.extract_join_keys(content) + ) yield msg_handler.create_and_send_event( { diff --git a/synapse/util/third_party_invites.py b/synapse/util/third_party_invites.py new file mode 100644 index 0000000000..b7e38c7ec3 --- /dev/null +++ b/synapse/util/third_party_invites.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 OpenMarket Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from twisted.internet import defer +from synapse.api.errors import AuthError + + +INVITE_KEYS = {"id_server", "medium", "address", "display_name"} + +JOIN_KEYS = { + "token", + "public_key", + "key_validity_url", + "signature", + "sender", +} + + +def has_invite_keys(content): + for key in INVITE_KEYS: + if key not in content: + return False + return True + + +def has_join_keys(content): + for key in JOIN_KEYS: + if key not in content: + return False + return True + + +def join_has_third_party_invite(content): + if "third_party_invite" not in content: + return False + return has_join_keys(content["third_party_invite"]) + + +def extract_join_keys(src): + return { + key: value + for key, value in src["third_party_invite"].items() + if key in JOIN_KEYS + } + + +@defer.inlineCallbacks +def check_key_valid(http_client, event): + try: + response = yield http_client.get_json( + event.content["third_party_invite"]["key_validity_url"], + {"public_key": event.content["third_party_invite"]["public_key"]} + ) + if not response["valid"]: + raise AuthError(403, "Third party certificate was invalid") + except IOError: + raise AuthError(403, "Third party certificate could not be checked") diff --git a/synapse/util/thirdpartyinvites.py b/synapse/util/thirdpartyinvites.py deleted file mode 100644 index ad0f4e88e9..0000000000 --- a/synapse/util/thirdpartyinvites.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2015 OpenMarket Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from twisted.internet import defer -from synapse.api.errors import AuthError - - -class ThirdPartyInvites(object): - INVITE_KEYS = {"id_server", "medium", "address", "display_name"} - - JOIN_KEYS = { - "token", - "public_key", - "key_validity_url", - "signature", - "sender", - } - - @classmethod - def has_invite_keys(cls, content): - for key in cls.INVITE_KEYS: - if key not in content: - return False - return True - - @classmethod - def has_join_keys(cls, content): - for key in cls.JOIN_KEYS: - if key not in content: - return False - return True - - @classmethod - def join_has_third_party_invite(cls, content): - if "third_party_invite" not in content: - return False - return cls.has_join_keys(content["third_party_invite"]) - - @classmethod - def copy_join_keys(cls, src, dst): - for key in cls.JOIN_KEYS: - if key in src: - dst[key] = src[key] - - @classmethod - @defer.inlineCallbacks - def check_key_valid(cls, http_client, event): - try: - response = yield http_client.get_json( - event.content["third_party_invite"]["key_validity_url"], - {"public_key": event.content["third_party_invite"]["public_key"]} - ) - if not response["valid"]: - raise AuthError(403, "Third party certificate was invalid") - except IOError: - raise AuthError(403, "Third party certificate could not be checked") -- cgit 1.5.1 From b8dd5b1a2d76f0426c600ae19ea9d9612e5327dc Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 16 Oct 2015 14:54:54 +0100 Subject: Verify third party ID server certificates --- synapse/api/auth.py | 11 +++++++++++ synapse/handlers/room.py | 31 +++++++++++++++++++++++++++++-- synapse/http/client.py | 4 ---- synapse/util/third_party_invites.py | 6 +++--- 4 files changed, 43 insertions(+), 9 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index e96d747b99..aee9b8a14f 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -397,13 +397,24 @@ class Auth(object): (EventTypes.ThirdPartyInvite, token,) ) if not invite_event: + logger.info("Failing 3pid invite because no invite found for token %s", token) return False try: public_key = join_third_party_invite["public_key"] key_validity_url = join_third_party_invite["key_validity_url"] if invite_event.content["public_key"] != public_key: + logger.info( + "Failing 3pid invite because public key invite: %s != join: %s", + invite_event.content["public_key"], + public_key + ) return False if invite_event.content["key_validity_url"] != key_validity_url: + logger.info( + "Failing 3pid invite because key_validity_url invite: %s != join: %s", + invite_event.content["key_validity_url"], + key_validity_url + ) return False verify_key = nacl.signing.VerifyKey(decode_base64(public_key)) encoded_signature = join_third_party_invite["signature"] diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 9ffa521aad..3f0cde56f0 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -22,11 +22,16 @@ from synapse.types import UserID, RoomAlias, RoomID from synapse.api.constants import ( EventTypes, Membership, JoinRules, RoomCreationPreset, ) -from synapse.api.errors import StoreError, SynapseError +from synapse.api.errors import AuthError, StoreError, SynapseError from synapse.util import stringutils, unwrapFirstError from synapse.util.async import run_on_reactor +from signedjson.sign import verify_signed_json +from signedjson.key import decode_verify_key_bytes + from collections import OrderedDict +from unpaddedbase64 import decode_base64 + import logging import string @@ -614,12 +619,34 @@ class RoomMemberHandler(BaseHandler): ) if "mxid" in data: - # TODO: Validate the response signature and such + if "signatures" not in data: + raise AuthError(401, "No signatures on 3pid binding") + self.verify_any_signature(data, id_server) defer.returnValue(data["mxid"]) + except IOError as e: logger.warn("Error from identity server lookup: %s" % (e,)) defer.returnValue(None) + @defer.inlineCallbacks + def verify_any_signature(self, data, server_hostname): + if server_hostname not in data["signatures"]: + raise AuthError(401, "No signature from server %s" % (server_hostname,)) + for key_name, signature in data["signatures"][server_hostname].items(): + key_data = yield self.hs.get_simple_http_client().get_json( + "https://%s/_matrix/identity/api/v1/pubkey/%s" % + (server_hostname, key_name,), + ) + if "public_key" not in key_data: + raise AuthError(401, "No public key named %s from %s" % + (key_name, server_hostname,)) + verify_signed_json( + data, + server_hostname, + decode_verify_key_bytes(key_name, decode_base64(key_data["public_key"])) + ) + return + @defer.inlineCallbacks def _make_and_store_3pid_invite( self, diff --git a/synapse/http/client.py b/synapse/http/client.py index 9a5869abee..27e5190224 100644 --- a/synapse/http/client.py +++ b/synapse/http/client.py @@ -24,7 +24,6 @@ from canonicaljson import encode_canonical_json from twisted.internet import defer, reactor, ssl from twisted.web.client import ( Agent, readBody, FileBodyProducer, PartialDownloadError, - HTTPConnectionPool, ) from twisted.web.http_headers import Headers @@ -59,11 +58,8 @@ class SimpleHttpClient(object): # The default context factory in Twisted 14.0.0 (which we require) is # BrowserLikePolicyForHTTPS which will do regular cert validation # 'like a browser' - pool = HTTPConnectionPool(reactor) - pool.maxPersistentPerHost = 10 self.agent = Agent( reactor, - pool=pool, connectTimeout=15, contextFactory=hs.get_http_client_context_factory() ) diff --git a/synapse/util/third_party_invites.py b/synapse/util/third_party_invites.py index 41e597d5b9..335a9755b2 100644 --- a/synapse/util/third_party_invites.py +++ b/synapse/util/third_party_invites.py @@ -63,7 +63,7 @@ def check_key_valid(http_client, event): event.content["third_party_invite"]["key_validity_url"], {"public_key": event.content["third_party_invite"]["public_key"]} ) - if not response["valid"]: - raise AuthError(403, "Third party certificate was invalid") - except IOError: + except Exception: raise AuthError(502, "Third party certificate could not be checked") + if "valid" not in response or not response["valid"]: + raise AuthError(403, "Third party certificate was invalid") -- cgit 1.5.1 From c225d63e9e50226dce510dda298ad3877460e69a Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 16 Oct 2015 15:07:56 +0100 Subject: Add signing host and keyname to signatures --- synapse/api/auth.py | 14 +++++++++----- synapse/util/third_party_invites.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'synapse/api') diff --git a/synapse/api/auth.py b/synapse/api/auth.py index aee9b8a14f..5c83aafa7d 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -416,11 +416,15 @@ class Auth(object): key_validity_url ) return False - verify_key = nacl.signing.VerifyKey(decode_base64(public_key)) - encoded_signature = join_third_party_invite["signature"] - signature = decode_base64(encoded_signature) - verify_key.verify(token, signature) - return True + for _, signature_block in join_third_party_invite["signatures"].items(): + for key_name, encoded_signature in signature_block.items(): + if not key_name.startswith("ed25519:"): + return False + verify_key = nacl.signing.VerifyKey(decode_base64(public_key)) + signature = decode_base64(encoded_signature) + verify_key.verify(token, signature) + return True + return False except (KeyError, BadSignatureError,): return False diff --git a/synapse/util/third_party_invites.py b/synapse/util/third_party_invites.py index 335a9755b2..792db5ba39 100644 --- a/synapse/util/third_party_invites.py +++ b/synapse/util/third_party_invites.py @@ -23,7 +23,7 @@ JOIN_KEYS = { "token", "public_key", "key_validity_url", - "signature", + "signatures", "sender", } -- cgit 1.5.1