Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

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

Merged
merged 10 commits into from
Jun 10, 2020
1 change: 1 addition & 0 deletions changelog.d/7639.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add an option to enable encryption by default for new rooms.
20 changes: 20 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,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
Expand Down
5 changes: 5 additions & 0 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions synapse/config/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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
Expand Down Expand Up @@ -79,6 +80,7 @@ class HomeServerConfig(RootConfig):
PasswordAuthProviderConfig,
PushConfig,
SpamCheckerConfig,
RoomConfig,
GroupsConfig,
UserDirectoryConfig,
ConsentConfig,
Expand Down
80 changes: 80 additions & 0 deletions synapse/config/room.py
Original file line number Diff line number Diff line change
@@ -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
"""
12 changes: 10 additions & 2 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
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,
Expand Down Expand Up @@ -742,7 +747,10 @@ async def _process_received_pdu(
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)]
Expand Down
74 changes: 47 additions & 27 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@

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
Expand Down Expand Up @@ -56,31 +61,6 @@


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)

Expand All @@ -89,6 +69,39 @@ def __init__(self, hs):
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
Expand Down Expand Up @@ -798,7 +811,7 @@ async def send(etype, content, **kwargs) -> int:
)
return last_stream_id

config = RoomCreationHandler.PRESETS_DICT[preset_config]
config = self._presets_dict[preset_config]

creator_id = creator.user.to_string()

Expand Down Expand Up @@ -888,6 +901,13 @@ async def send(etype, content, **kwargs) -> int:
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(
Expand Down
6 changes: 5 additions & 1 deletion tests/federation/test_federation_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

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
Expand Down Expand Up @@ -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),
Expand Down
18 changes: 14 additions & 4 deletions tests/handlers/test_e2e_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import synapse.handlers.e2e_keys
import synapse.storage
from synapse.api import errors
from synapse.api.constants import RoomEncryptionAlgorithms

from tests import unittest, utils

Expand Down Expand Up @@ -222,7 +223,10 @@ def test_reupload_signatures(self):
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",
Expand All @@ -232,7 +236,10 @@ def test_reupload_signatures(self):
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",
Expand Down Expand Up @@ -315,7 +322,10 @@ def test_upload_signatures(self):
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"}},
}
Expand Down Expand Up @@ -392,7 +402,7 @@ def test_upload_signatures(self):
"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",
Expand Down
Loading