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

Config option for verifying federation certificates (MSC 1711) #4967

Merged
merged 57 commits into from
Apr 25, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
dbb3319
Config option for verifying federation certificates
anoadragon453 Mar 28, 2019
5fd4cd0
Whitelist per domain
anoadragon453 Mar 29, 2019
9146059
Ability to specify list of custom CA certificates
anoadragon453 Apr 1, 2019
4d1002f
Documentation of new options
anoadragon453 Apr 1, 2019
904ea6c
Add changelog
anoadragon453 Apr 1, 2019
8e89fc5
fixes and lint
anoadragon453 Apr 1, 2019
6de7ab8
flake8 lied to me :c
anoadragon453 Apr 1, 2019
8b1c459
lint
anoadragon453 Apr 1, 2019
ffc9c10
punctuation
anoadragon453 Apr 1, 2019
da23aa2
Cleaner code logic
anoadragon453 Apr 1, 2019
2325928
consolidate logic
anoadragon453 Apr 1, 2019
0ce5b5b
words
anoadragon453 Apr 1, 2019
2851e64
Generate config and remove extra newline
anoadragon453 Apr 1, 2019
d95b4ef
No tls.
anoadragon453 Apr 1, 2019
0f07754
Fix documentation
anoadragon453 Apr 1, 2019
86dfaf4
Merge branch 'develop' into anoa/msc_1711
anoadragon453 Apr 1, 2019
9d27f8e
Cache config
anoadragon453 Apr 1, 2019
4f177c5
again
anoadragon453 Apr 1, 2019
5575f7a
again
anoadragon453 Apr 1, 2019
ee0c7e1
again
anoadragon453 Apr 1, 2019
a7d7c5a
Don't run validation code if validation is turned off
anoadragon453 Apr 2, 2019
fec0c9a
Remove TODO
anoadragon453 Apr 2, 2019
aeffa4d
Use platformTrust instead of verify=True
anoadragon453 Apr 2, 2019
821baa4
Give tests config with default config values
anoadragon453 Apr 2, 2019
40702b6
Check for type instead of not None
anoadragon453 Apr 2, 2019
cc149b1
Give MFA config access
anoadragon453 Apr 2, 2019
8647013
lint
anoadragon453 Apr 2, 2019
d194e5d
liiint
anoadragon453 Apr 2, 2019
3adf15d
Correct imports
anoadragon453 Apr 2, 2019
61a39a4
isort
anoadragon453 Apr 2, 2019
3e29d45
isort
anoadragon453 Apr 2, 2019
f38da61
Pass config
anoadragon453 Apr 2, 2019
3f1fe92
Shuffle config
anoadragon453 Apr 2, 2019
a8adde0
Restart build
anoadragon453 Apr 2, 2019
e083ae3
Address changes. Make federation_domain_whitelist not None
anoadragon453 Apr 2, 2019
a87b556
regen sample config
anoadragon453 Apr 2, 2019
983474d
Remove turning cert validation off from faq
anoadragon453 Apr 3, 2019
1fd5680
Don't laugh at my own jokes
anoadragon453 Apr 3, 2019
7c432de
Simplify with better exception handling
anoadragon453 Apr 3, 2019
4f03528
Raise config error if empty ca list
anoadragon453 Apr 3, 2019
892c71d
Change test defaults
anoadragon453 Apr 3, 2019
a5ab4af
None is very different from []
anoadragon453 Apr 3, 2019
999f7db
Don't break logic when refactoring
anoadragon453 Apr 3, 2019
507cdf2
fix domain whitelist
anoadragon453 Apr 3, 2019
9bd1432
regen config
anoadragon453 Apr 3, 2019
d3f926a
regen
anoadragon453 Apr 3, 2019
09f6622
provide an arg to default_config
anoadragon453 Apr 3, 2019
e337c2d
Addressed changes
anoadragon453 Apr 4, 2019
433db40
Address changes
anoadragon453 Apr 4, 2019
396eb64
Change federation whitelist stuff back
anoadragon453 Apr 4, 2019
3dbad06
lint
anoadragon453 Apr 4, 2019
595d6ec
fix domain_whitelist
anoadragon453 Apr 4, 2019
edf2dd4
sample confiiiiiiiiiiiiiiiiiiiiiiig
anoadragon453 Apr 4, 2019
93850f0
domain globbing
anoadragon453 Apr 4, 2019
6691998
Add comment and simplify diff
anoadragon453 Apr 8, 2019
92cc6b0
Heavier warning about disabling TLS verification
anoadragon453 Apr 8, 2019
bc4b148
regen sample config
anoadragon453 Apr 8, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions docs/MSC1711_certificates_FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,28 +177,6 @@ You can do this with a `.well-known` file as follows:
on `customer.example.net:8000` it correctly handles HTTP requests with
Host header set to `customer.example.net:8000`.

## Turning off certificate validation

It is possible to turn off certificate validation for remote servers, but
note that this must be explicitly enabled and is thus only suitable for
private federations. This will only disable TLS certificate validation on
federation endpoints; other requests made to recaptcha, identity services
etc. will be unaffected.

```
federation_verify_certificates = false
```

You can also only disable certificate validation for a specific set of
homeservers:

```
federation_certificate_verification_whitelist:
- subdomain.my-server.org
- example.org
- 1.2.3.4
```

## Specifying your own Certificate Authorities
richvdh marked this conversation as resolved.
Show resolved Hide resolved

If you would like to specify your own list of trusted Certificate
Expand Down
14 changes: 11 additions & 3 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,20 @@ listeners:

# Whether to verify TLS certificates when sending federation traffic.
#
# This currently defaults to `false`, however this will change in
# Synapse 1.0 when valid federation certificates will be required.
#
#federation_verify_certificates: true

# Prevent federation certificate validation on the following whitelist
# of domains. Only effective if federation_verify_certicates is true.
# Skip federation certificate verification on the following whitelist
# of domains.
#
# Note that this should only be used within the context of private
# federation as it will otherwise break things.
#
# Only effective if federation_verify_certicates is `true`.
#
#federation_certificate_validation_whitelist:
#federation_certificate_verification_whitelist:
# - lon.example.com
# - nyc.example.com
# - syd.example.com
Expand Down
14 changes: 8 additions & 6 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,17 @@ def read_config(self, config):
self.admin_contact = config.get("admin_contact", None)

# FIXME: federation_domain_whitelist needs sytests
self.federation_domain_whitelist = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this code being changed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left over from previous refactor that was reverted. Have changed code back so there's less diff now.

federation_domain_whitelist = config.get(
"federation_domain_whitelist", None
"federation_domain_whitelist", [],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hrm, could we keep changes to federation_domain_whitelist in a separate PR? there's already quite a lot going on here and it's hard to keep track of an extra dimension of changes.

)
# turn the whitelist into a hash for speed of lookup
if federation_domain_whitelist is not None:

self.federation_domain_whitelist = None
if len(federation_domain_whitelist) > 0:
self.federation_domain_whitelist = {}
for domain in federation_domain_whitelist:
self.federation_domain_whitelist[domain] = True

# turn the whitelist into a hash for speed of lookup
for domain in federation_domain_whitelist:
self.federation_domain_whitelist[domain] = True

if self.public_baseurl is not None:
if self.public_baseurl[-1] != '/':
Expand Down
53 changes: 29 additions & 24 deletions synapse/config/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,17 @@ def read_config(self, config):
)

# Whitelist of domains to not verify certificates for
self.federation_certificate_verification_whitelist = None
federation_certificate_verification_whitelist = config.get(
"federation_certificate_verification_whitelist", None
"federation_certificate_verification_whitelist", [],
)

# Store whitelisted domains in a hash for fast lookup
if federation_certificate_verification_whitelist is not None:
self.federation_certificate_verification_whitelist = None
if len(federation_certificate_verification_whitelist) > 0:
richvdh marked this conversation as resolved.
Show resolved Hide resolved
self.federation_certificate_verification_whitelist = {}
for domain in federation_certificate_verification_whitelist:
self.federation_certificate_verification_whitelist[domain] = True

# Store whitelisted domains in a hash for fast lookup
for domain in federation_certificate_verification_whitelist:
self.federation_certificate_verification_whitelist[domain] = True

# List of custom certificate authorities for federation traffic validation
self.federation_custom_ca_list = config.get(
richvdh marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -95,26 +96,24 @@ def read_config(self, config):

# Read in and parse custom CA certificates
if self.federation_custom_ca_list is not None:
if self.federation_custom_ca_list:
raise ConfigError("federation_custom_ca_list specified without "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment as to why this is the right thing to do would be helpful.

"any certificate files")

certs = []
for ca_file in self.federation_custom_ca_list:
logger.debug("Reading custom CA certificate file: %s", ca_file)
try:
with open(ca_file, 'rb') as f:
content = f.read()
except Exception:
logger.exception("Failed to read custom CA certificate off disk!")
raise
content = self.read_file(ca_file)

# Parse the CA certificates
try:
cert_base = Certificate.loadPEM(content)
certs.append(cert_base)
except Exception:
logger.exception("Failed to parse custom CA certificate off disk!")
raise
except Exception as e:
raise ConfigError("Error parsing custom CA certificate file %s: %s"
% (ca_file, e))

if len(certs) > 0:
self.federation_custom_ca_list = trustRootFromCertificates(certs)
self.federation_custom_ca_list = trustRootFromCertificates(certs)

# This config option applies to non-federation HTTP clients
# (e.g. for talking to recaptcha, identity servers, and such)
Expand Down Expand Up @@ -146,14 +145,12 @@ def is_disk_cert_valid(self, allow_self_signed=True):
with open(self.tls_certificate_file, 'rb') as f:
cert_pem = f.read()
except Exception:
logger.exception("Failed to read existing certificate off disk!")
raise
richvdh marked this conversation as resolved.
Show resolved Hide resolved
logger.fatal("Failed to read existing certificate off disk")

try:
tls_certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
except Exception:
logger.exception("Failed to parse existing certificate off disk!")
raise
logger.fatal("Failed to parse existing certificate off disk")

if not allow_self_signed:
if tls_certificate.get_subject() == tls_certificate.get_issuer():
Expand Down Expand Up @@ -240,12 +237,20 @@ def default_config(self, config_dir_path, server_name, **kwargs):

# Whether to verify TLS certificates when sending federation traffic.
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
#
# This currently defaults to `false`, however this will change in
# Synapse 1.0 when valid federation certificates will be required.
#
#federation_verify_certificates: true

# Prevent federation certificate validation on the following whitelist
# of domains. Only effective if federation_verify_certicates is true.
# Skip federation certificate verification on the following whitelist
# of domains.
#
# Note that this should only be used within the context of private
richvdh marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also can you add this warning to federation_custom_ca_list.

# federation as it will otherwise break things.
#
# Only effective if federation_verify_certicates is `true`.
#
#federation_certificate_validation_whitelist:
#federation_certificate_verification_whitelist:
# - lon.example.com
# - nyc.example.com
# - syd.example.com
Expand Down
27 changes: 16 additions & 11 deletions synapse/crypto/context_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from zope.interface import implementer

from OpenSSL import SSL, crypto
from twisted.internet._sslverify import OpenSSLCertificateAuthorities, _defaultCurveName
from twisted.internet._sslverify import (
ClientTLSOptions as ClientTLSOptionsVerify,
richvdh marked this conversation as resolved.
Show resolved Hide resolved
_defaultCurveName,
)
from twisted.internet.abstract import isIPAddress, isIPv6Address
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
from twisted.internet.ssl import CertificateOptions, ContextFactory, platformTrust
Expand Down Expand Up @@ -128,19 +131,18 @@ class ClientTLSOptionsFactory(object):

def __init__(self, config):
self._config = config

self._options_novalidate = CertificateOptions()
self._options_noverify = CertificateOptions()

# Check if we're using a custom list of a CA certificates
if isinstance(config.federation_custom_ca_list, OpenSSLCertificateAuthorities):
self._options_validate = CertificateOptions(
if config.federation_custom_ca_list is not None:
richvdh marked this conversation as resolved.
Show resolved Hide resolved
self._options_verify = CertificateOptions(
# Use custom CA trusted root certs
trustRoot=config.federation_custom_ca_list,
)
return

# If not, verify using those provided by the operating environment
self._options_validate = CertificateOptions(
self._options_verify = CertificateOptions(
# Use CA root certs provided by OpenSSL
trustRoot=platformTrust(),
)
Expand All @@ -149,10 +151,13 @@ def get_options(self, host):
# Use _makeContext so that we get a fresh OpenSSL CTX each time.

# Check if certificate verification has been enabled
richvdh marked this conversation as resolved.
Show resolved Hide resolved
if (self._config.federation_verify_certificates and
host not in self._config.federation_certificate_validation_whitelist):
# Require verification
return ClientTLSOptions(host, self._options_validate._makeContext())
if (self._config.federation_verify_certificates):
# and if the host is whitelisted against it
if (self._config.federation_certificate_verification_whitelist and
host in self._config.federation_certificate_verification_whitelist):
return ClientTLSOptions(host, self._options_noverify._makeContext())

return ClientTLSOptionsVerify(host, self._options_verify._makeContext())

# Otherwise don't require verification
return ClientTLSOptions(host, self._options_novalidate._makeContext())
return ClientTLSOptions(host, self._options_noverify._makeContext())
6 changes: 2 additions & 4 deletions synapse/federation/transport/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,8 @@ def authenticate_request(self, request, content):
json_request["origin"] = origin
json_request["signatures"].setdefault(origin, {})[key] = sig

if (
self.federation_domain_whitelist is not None and
origin not in self.federation_domain_whitelist
):
if (self.federation_domain_whitelist is not None and
origin not in self.federation_domain_whitelist):
raise FederationDeniedError(origin)

if not json_request["signatures"]:
Expand Down
7 changes: 2 additions & 5 deletions synapse/http/matrixfederationclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ def __init__(self, hs, tls_client_options_factory):
self.agent = MatrixFederationAgent(
hs.get_reactor(),
tls_client_options_factory,
hs.config,
)
self.clock = hs.get_clock()
self._store = hs.get_datastore()
Expand Down Expand Up @@ -284,10 +283,8 @@ def _send_request(
else:
_sec_timeout = self.default_timeout

if (
self.hs.config.federation_domain_whitelist is not None and
request.destination not in self.hs.config.federation_domain_whitelist
):
if (self.hs.config.federation_domain_whitelist is not None and
request.destination not in self.hs.config.federation_domain_whitelist):
raise FederationDeniedError(request.destination)

limiter = yield synapse.util.retryutils.get_retry_limiter(
Expand Down
6 changes: 2 additions & 4 deletions synapse/rest/key/v2/remote_key_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,8 @@ def query_keys(self, request, query, query_remote_on_cache_miss=False):

store_queries = []
for server_name, key_ids in query.items():
if (
self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist
):
if (self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist):
logger.debug("Federation denied with %s", server_name)
continue

Expand Down
12 changes: 4 additions & 8 deletions synapse/rest/media/v1/media_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,8 @@ def get_remote_media(self, request, server_name, media_id, name):
Deferred: Resolves once a response has successfully been written
to request
"""
if (
self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist
):
if (self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist):
raise FederationDeniedError(server_name)

self.mark_recently_accessed(server_name, media_id)
Expand Down Expand Up @@ -271,10 +269,8 @@ def get_remote_media_info(self, server_name, media_id):
Returns:
Deferred[dict]: The media_info of the file
"""
if (
self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist
):
if (self.federation_domain_whitelist is not None and
server_name not in self.federation_domain_whitelist):
raise FederationDeniedError(server_name)

# We linearize here to ensure that we don't try and download remote
Expand Down
8 changes: 2 additions & 6 deletions tests/http/federation/test_matrix_federation_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from tests.http import ServerTLSContext
from tests.server import FakeTransport, ThreadedMemoryReactorClock
from tests.unittest import TestCase
from tests.utils import default_config

logger = logging.getLogger(__name__)

Expand All @@ -49,16 +50,11 @@ def setUp(self):

self.mock_resolver = Mock()

config = Mock()
config.federation_custom_ca_list = None
config.federation_verify_certificates = False
config.federation_certificate_validation_whitelist = []

self.well_known_cache = TTLCache("test_cache", timer=self.reactor.seconds)

self.agent = MatrixFederationAgent(
reactor=self.reactor,
tls_client_options_factory=ClientTLSOptionsFactory(config),
tls_client_options_factory=ClientTLSOptionsFactory(default_config("test")),
_well_known_tls_policy=TrustingTLSPolicyForHTTPS(),
_srv_resolver=self.mock_resolver,
_well_known_cache=self.well_known_cache,
Expand Down
3 changes: 3 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ def default_config(name):
config.email_enable_notifs = False
config.block_non_admin_invites = False
config.federation_domain_whitelist = None
config.federation_certificate_verification_whitelist = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment at line 126. These lines should be unnecessary. If you want to set the default config for all tests, update the config dict above (and add a comment to justify it)

config.federation_custom_ca_list = None
config.federation_verify_certificates = False
config.federation_rc_reject_limit = 10
config.federation_rc_sleep_limit = 10
config.federation_rc_sleep_delay = 100
Expand Down