Add option to enable encryption by default for new rooms (#7639)

Fixes https://github.com/matrix-org/synapse/issues/2431

Adds config option `encryption_enabled_by_default_for_room_type`, which determines whether encryption should be enabled with the default encryption algorithm in private or public rooms upon creation. Whether the room is private or public is decided based upon the room creation preset that is used.

Part of this PR is also pulling out all of the individual instances of `m.megolm.v1.aes-sha2` into a constant variable to eliminate typos ala https://github.com/matrix-org/synapse/pull/7637

Based on #7637
This commit is contained in:
Andrew Morgan 2020-06-10 17:44:34 +01:00 committed by GitHub
parent 191dc98f80
commit fcd6961441
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 275 additions and 36 deletions

1
changelog.d/7639.feature Normal file
View file

@ -0,0 +1 @@
Add an option to enable encryption by default for new rooms.

View file

@ -1973,6 +1973,26 @@ spam_checker:
# example_stop_events_from: ['@bad:example.com']
## Rooms ##
# Controls whether locally-created rooms should be end-to-end encrypted by
# default.
#
# Possible options are "all", "invite", and "off". They are defined as:
#
# * "all": any locally-created room
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
# room creation presets
# * "off": this option will take no effect
#
# The default value is "off".
#
# Note that this option will only affect rooms created after it is set. It
# will also not affect rooms created by other servers.
#
#encryption_enabled_by_default_for_room_type: invite
# Uncomment to allow non-server-admin users to create groups on this server
#
#enable_group_creation: true

View file

@ -150,3 +150,8 @@ class EventContentFields(object):
# Timestamp to delete the event after
# cf https://github.com/matrix-org/matrix-doc/pull/2228
SELF_DESTRUCT_AFTER = "org.matrix.self_destruct_after"
class RoomEncryptionAlgorithms(object):
MEGOLM_V1_AES_SHA2 = "m.megolm.v1.aes-sha2"
DEFAULT = MEGOLM_V1_AES_SHA2

View file

@ -36,6 +36,7 @@ from .ratelimiting import RatelimitConfig
from .redis import RedisConfig
from .registration import RegistrationConfig
from .repository import ContentRepositoryConfig
from .room import RoomConfig
from .room_directory import RoomDirectoryConfig
from .saml2_config import SAML2Config
from .server import ServerConfig
@ -79,6 +80,7 @@ class HomeServerConfig(RootConfig):
PasswordAuthProviderConfig,
PushConfig,
SpamCheckerConfig,
RoomConfig,
GroupsConfig,
UserDirectoryConfig,
ConsentConfig,

80
synapse/config/room.py Normal file
View file

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# 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.
import logging
from synapse.api.constants import RoomCreationPreset
from ._base import Config, ConfigError
logger = logging.Logger(__name__)
class RoomDefaultEncryptionTypes(object):
"""Possible values for the encryption_enabled_by_default_for_room_type config option"""
ALL = "all"
INVITE = "invite"
OFF = "off"
class RoomConfig(Config):
section = "room"
def read_config(self, config, **kwargs):
# Whether new, locally-created rooms should have encryption enabled
encryption_for_room_type = config.get(
"encryption_enabled_by_default_for_room_type",
RoomDefaultEncryptionTypes.OFF,
)
if encryption_for_room_type == RoomDefaultEncryptionTypes.ALL:
self.encryption_enabled_by_default_for_room_presets = [
RoomCreationPreset.PRIVATE_CHAT,
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
RoomCreationPreset.PUBLIC_CHAT,
]
elif encryption_for_room_type == RoomDefaultEncryptionTypes.INVITE:
self.encryption_enabled_by_default_for_room_presets = [
RoomCreationPreset.PRIVATE_CHAT,
RoomCreationPreset.TRUSTED_PRIVATE_CHAT,
]
elif encryption_for_room_type == RoomDefaultEncryptionTypes.OFF:
self.encryption_enabled_by_default_for_room_presets = []
else:
raise ConfigError(
"Invalid value for encryption_enabled_by_default_for_room_type"
)
def generate_config_section(self, **kwargs):
return """\
## Rooms ##
# Controls whether locally-created rooms should be end-to-end encrypted by
# default.
#
# Possible options are "all", "invite", and "off". They are defined as:
#
# * "all": any locally-created room
# * "invite": any room created with the "private_chat" or "trusted_private_chat"
# room creation presets
# * "off": this option will take no effect
#
# The default value is "off".
#
# Note that this option will only affect rooms created after it is set. It
# will also not affect rooms created by other servers.
#
#encryption_enabled_by_default_for_room_type: invite
"""

View file

@ -33,7 +33,12 @@ from unpaddedbase64 import decode_base64
from twisted.internet import defer
from synapse import event_auth
from synapse.api.constants import EventTypes, Membership, RejectedReason
from synapse.api.constants import (
EventTypes,
Membership,
RejectedReason,
RoomEncryptionAlgorithms,
)
from synapse.api.errors import (
AuthError,
CodeMessageException,
@ -742,7 +747,10 @@ class FederationHandler(BaseHandler):
if device:
keys = device.get("keys", {}).get("keys", {})
if event.content.get("algorithm") == "m.megolm.v1.aes-sha2":
if (
event.content.get("algorithm")
== RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2
):
# For this algorithm we expect a curve25519 key.
key_name = "curve25519:%s" % (device_id,)
current_keys = [keys.get(key_name)]

View file

@ -26,7 +26,12 @@ from typing import Tuple
from six import iteritems, string_types
from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset
from synapse.api.constants import (
EventTypes,
JoinRules,
RoomCreationPreset,
RoomEncryptionAlgorithms,
)
from synapse.api.errors import AuthError, Codes, NotFoundError, StoreError, SynapseError
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.events.utils import copy_power_levels_contents
@ -56,31 +61,6 @@ FIVE_MINUTES_IN_MS = 5 * 60 * 1000
class RoomCreationHandler(BaseHandler):
PRESETS_DICT = {
RoomCreationPreset.PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": True,
"power_level_content_override": {"invite": 0},
},
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": True,
"guest_can_join": True,
"power_level_content_override": {"invite": 0},
},
RoomCreationPreset.PUBLIC_CHAT: {
"join_rules": JoinRules.PUBLIC,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": False,
"power_level_content_override": {},
},
}
def __init__(self, hs):
super(RoomCreationHandler, self).__init__(hs)
@ -89,6 +69,39 @@ class RoomCreationHandler(BaseHandler):
self.room_member_handler = hs.get_room_member_handler()
self.config = hs.config
# Room state based off defined presets
self._presets_dict = {
RoomCreationPreset.PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": True,
"power_level_content_override": {"invite": 0},
},
RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
"join_rules": JoinRules.INVITE,
"history_visibility": "shared",
"original_invitees_have_ops": True,
"guest_can_join": True,
"power_level_content_override": {"invite": 0},
},
RoomCreationPreset.PUBLIC_CHAT: {
"join_rules": JoinRules.PUBLIC,
"history_visibility": "shared",
"original_invitees_have_ops": False,
"guest_can_join": False,
"power_level_content_override": {},
},
}
# Modify presets to selectively enable encryption by default per homeserver config
for preset_name, preset_config in self._presets_dict.items():
encrypted = (
preset_name
in self.config.encryption_enabled_by_default_for_room_presets
)
preset_config["encrypted"] = encrypted
self._replication = hs.get_replication_data_handler()
# linearizer to stop two upgrades happening at once
@ -798,7 +811,7 @@ class RoomCreationHandler(BaseHandler):
)
return last_stream_id
config = RoomCreationHandler.PRESETS_DICT[preset_config]
config = self._presets_dict[preset_config]
creator_id = creator.user.to_string()
@ -888,6 +901,13 @@ class RoomCreationHandler(BaseHandler):
etype=etype, state_key=state_key, content=content
)
if config["encrypted"]:
last_sent_stream_id = await send(
etype=EventTypes.RoomEncryption,
state_key="",
content={"algorithm": RoomEncryptionAlgorithms.DEFAULT},
)
return last_sent_stream_id
async def _generate_room_id(

View file

@ -21,6 +21,7 @@ from signedjson.types import BaseKey, SigningKey
from twisted.internet import defer
from synapse.api.constants import RoomEncryptionAlgorithms
from synapse.rest import admin
from synapse.rest.client.v1 import login
from synapse.types import JsonDict, ReadReceipt
@ -536,7 +537,10 @@ def build_device_dict(user_id: str, device_id: str, sk: SigningKey):
return {
"user_id": user_id,
"device_id": device_id,
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
"algorithms": [
"m.olm.curve25519-aes-sha2",
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
],
"keys": {
"curve25519:" + device_id: "curve25519+key",
key_id(sk): encode_pubkey(sk),

View file

@ -25,6 +25,7 @@ from twisted.internet import defer
import synapse.handlers.e2e_keys
import synapse.storage
from synapse.api import errors
from synapse.api.constants import RoomEncryptionAlgorithms
from tests import unittest, utils
@ -222,7 +223,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
device_key_1 = {
"user_id": local_user,
"device_id": "abc",
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
"algorithms": [
"m.olm.curve25519-aes-sha2",
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
],
"keys": {
"ed25519:abc": "base64+ed25519+key",
"curve25519:abc": "base64+curve25519+key",
@ -232,7 +236,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
device_key_2 = {
"user_id": local_user,
"device_id": "def",
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
"algorithms": [
"m.olm.curve25519-aes-sha2",
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
],
"keys": {
"ed25519:def": "base64+ed25519+key",
"curve25519:def": "base64+curve25519+key",
@ -315,7 +322,10 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
device_key = {
"user_id": local_user,
"device_id": device_id,
"algorithms": ["m.olm.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
"algorithms": [
"m.olm.curve25519-aes-sha2",
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
],
"keys": {"curve25519:xyz": "curve25519+key", "ed25519:xyz": device_pubkey},
"signatures": {local_user: {"ed25519:xyz": "something"}},
}
@ -392,7 +402,7 @@ class E2eKeysHandlerTestCase(unittest.TestCase):
"device_id": device_id,
"algorithms": [
"m.olm.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2",
RoomEncryptionAlgorithms.MEGOLM_V1_AES_SHA2,
],
"keys": {
"curve25519:xyz": "curve25519+key",

View file

@ -17,12 +17,13 @@ from mock import Mock
from twisted.internet import defer
import synapse.rest.admin
from synapse.api.constants import UserTypes
from synapse.api.constants import EventTypes, RoomEncryptionAlgorithms, UserTypes
from synapse.rest.client.v1 import login, room
from synapse.rest.client.v2_alpha import user_directory
from synapse.storage.roommember import ProfileInfo
from tests import unittest
from tests.unittest import override_config
class UserDirectoryTestCase(unittest.HomeserverTestCase):
@ -147,6 +148,94 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
s = self.get_success(self.handler.search_users(u1, "user3", 10))
self.assertEqual(len(s["results"]), 0)
@override_config({"encryption_enabled_by_default_for_room_type": "all"})
def test_encrypted_by_default_config_option_all(self):
"""Tests that invite-only and non-invite-only rooms have encryption enabled by
default when the config option encryption_enabled_by_default_for_room_type is "all".
"""
# Create a user
user = self.register_user("user", "pass")
user_token = self.login(user, "pass")
# Create an invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
# Check that the room has an encryption state event
event_content = self.helper.get_state(
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
)
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
# Create a non invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
# Check that the room has an encryption state event
event_content = self.helper.get_state(
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
)
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
@override_config({"encryption_enabled_by_default_for_room_type": "invite"})
def test_encrypted_by_default_config_option_invite(self):
"""Tests that only new, invite-only rooms have encryption enabled by default when
the config option encryption_enabled_by_default_for_room_type is "invite".
"""
# Create a user
user = self.register_user("user", "pass")
user_token = self.login(user, "pass")
# Create an invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
# Check that the room has an encryption state event
event_content = self.helper.get_state(
room_id=room_id, event_type=EventTypes.RoomEncryption, tok=user_token,
)
self.assertEqual(event_content, {"algorithm": RoomEncryptionAlgorithms.DEFAULT})
# Create a non invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
# Check that the room does not have an encryption state event
self.helper.get_state(
room_id=room_id,
event_type=EventTypes.RoomEncryption,
tok=user_token,
expect_code=404,
)
@override_config({"encryption_enabled_by_default_for_room_type": "off"})
def test_encrypted_by_default_config_option_off(self):
"""Tests that neither new invite-only nor non-invite-only rooms have encryption
enabled by default when the config option
encryption_enabled_by_default_for_room_type is "off".
"""
# Create a user
user = self.register_user("user", "pass")
user_token = self.login(user, "pass")
# Create an invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=False, tok=user_token)
# Check that the room does not have an encryption state event
self.helper.get_state(
room_id=room_id,
event_type=EventTypes.RoomEncryption,
tok=user_token,
expect_code=404,
)
# Create a non invite-only room as that user
room_id = self.helper.create_room_as(user, is_public=True, tok=user_token)
# Check that the room does not have an encryption state event
self.helper.get_state(
room_id=room_id,
event_type=EventTypes.RoomEncryption,
tok=user_token,
expect_code=404,
)
def test_spam_checker(self):
"""
A user which fails to the spam checks will not appear in search results.

View file

@ -30,7 +30,7 @@ class MessageAcceptTests(unittest.HomeserverTestCase):
room_creator = self.homeserver.get_room_creation_handler()
room_deferred = ensureDeferred(
room_creator.create_room(
our_user, room_creator.PRESETS_DICT["public_chat"], ratelimit=False
our_user, room_creator._presets_dict["public_chat"], ratelimit=False
)
)
self.reactor.advance(0.1)