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
code_spécifique_watcha
Andrew Morgan 5 years ago committed by GitHub
parent 191dc98f80
commit fcd6961441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      changelog.d/7639.feature
  2. 20
      docs/sample_config.yaml
  3. 5
      synapse/api/constants.py
  4. 2
      synapse/config/homeserver.py
  5. 80
      synapse/config/room.py
  6. 12
      synapse/handlers/federation.py
  7. 74
      synapse/handlers/room.py
  8. 6
      tests/federation/test_federation_sender.py
  9. 18
      tests/handlers/test_e2e_keys.py
  10. 91
      tests/handlers/test_user_directory.py
  11. 2
      tests/test_federation.py

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

@ -1973,6 +1973,26 @@ spam_checker:
# example_stop_events_from: ['@bad:example.com'] # 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 # Uncomment to allow non-server-admin users to create groups on this server
# #
#enable_group_creation: true #enable_group_creation: true

@ -150,3 +150,8 @@ class EventContentFields(object):
# Timestamp to delete the event after # Timestamp to delete the event after
# cf https://github.com/matrix-org/matrix-doc/pull/2228 # cf https://github.com/matrix-org/matrix-doc/pull/2228
SELF_DESTRUCT_AFTER = "org.matrix.self_destruct_after" 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

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

@ -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
"""

@ -33,7 +33,12 @@ from unpaddedbase64 import decode_base64
from twisted.internet import defer from twisted.internet import defer
from synapse import event_auth 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 ( from synapse.api.errors import (
AuthError, AuthError,
CodeMessageException, CodeMessageException,
@ -742,7 +747,10 @@ class FederationHandler(BaseHandler):
if device: if device:
keys = device.get("keys", {}).get("keys", {}) 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. # For this algorithm we expect a curve25519 key.
key_name = "curve25519:%s" % (device_id,) key_name = "curve25519:%s" % (device_id,)
current_keys = [keys.get(key_name)] current_keys = [keys.get(key_name)]

@ -26,7 +26,12 @@ from typing import Tuple
from six import iteritems, string_types 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.errors import AuthError, Codes, NotFoundError, StoreError, SynapseError
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.events.utils import copy_power_levels_contents from synapse.events.utils import copy_power_levels_contents
@ -56,31 +61,6 @@ FIVE_MINUTES_IN_MS = 5 * 60 * 1000
class RoomCreationHandler(BaseHandler): 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): def __init__(self, hs):
super(RoomCreationHandler, self).__init__(hs) super(RoomCreationHandler, self).__init__(hs)
@ -89,6 +69,39 @@ class RoomCreationHandler(BaseHandler):
self.room_member_handler = hs.get_room_member_handler() self.room_member_handler = hs.get_room_member_handler()
self.config = hs.config 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() self._replication = hs.get_replication_data_handler()
# linearizer to stop two upgrades happening at once # linearizer to stop two upgrades happening at once
@ -798,7 +811,7 @@ class RoomCreationHandler(BaseHandler):
) )
return last_stream_id return last_stream_id
config = RoomCreationHandler.PRESETS_DICT[preset_config] config = self._presets_dict[preset_config]
creator_id = creator.user.to_string() creator_id = creator.user.to_string()
@ -888,6 +901,13 @@ class RoomCreationHandler(BaseHandler):
etype=etype, state_key=state_key, content=content 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 return last_sent_stream_id
async def _generate_room_id( async def _generate_room_id(

@ -21,6 +21,7 @@ from signedjson.types import BaseKey, SigningKey
from twisted.internet import defer from twisted.internet import defer
from synapse.api.constants import RoomEncryptionAlgorithms
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client.v1 import login from synapse.rest.client.v1 import login
from synapse.types import JsonDict, ReadReceipt from synapse.types import JsonDict, ReadReceipt
@ -536,7 +537,10 @@ def build_device_dict(user_id: str, device_id: str, sk: SigningKey):
return { return {
"user_id": user_id, "user_id": user_id,
"device_id": device_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": { "keys": {
"curve25519:" + device_id: "curve25519+key", "curve25519:" + device_id: "curve25519+key",
key_id(sk): encode_pubkey(sk), key_id(sk): encode_pubkey(sk),

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

@ -17,12 +17,13 @@ from mock import Mock
from twisted.internet import defer from twisted.internet import defer
import synapse.rest.admin 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.v1 import login, room
from synapse.rest.client.v2_alpha import user_directory from synapse.rest.client.v2_alpha import user_directory
from synapse.storage.roommember import ProfileInfo from synapse.storage.roommember import ProfileInfo
from tests import unittest from tests import unittest
from tests.unittest import override_config
class UserDirectoryTestCase(unittest.HomeserverTestCase): class UserDirectoryTestCase(unittest.HomeserverTestCase):
@ -147,6 +148,94 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
s = self.get_success(self.handler.search_users(u1, "user3", 10)) s = self.get_success(self.handler.search_users(u1, "user3", 10))
self.assertEqual(len(s["results"]), 0) 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): def test_spam_checker(self):
""" """
A user which fails to the spam checks will not appear in search results. A user which fails to the spam checks will not appear in search results.

@ -30,7 +30,7 @@ class MessageAcceptTests(unittest.HomeserverTestCase):
room_creator = self.homeserver.get_room_creation_handler() room_creator = self.homeserver.get_room_creation_handler()
room_deferred = ensureDeferred( room_deferred = ensureDeferred(
room_creator.create_room( 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) self.reactor.advance(0.1)

Loading…
Cancel
Save