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

Commit

Permalink
Config option for verifying federation certificates (MSC 1711) (#4967)
Browse files Browse the repository at this point in the history
  • Loading branch information
anoadragon453 authored and richvdh committed Apr 25, 2019
1 parent 788163e commit 6824ddd
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 17 deletions.
1 change: 1 addition & 0 deletions changelog.d/4967.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implementation of [MSC1711](https://github.com/matrix-org/matrix-doc/pull/1711) including config options for requiring valid TLS certificates for federation traffic, the ability to disable TLS validation for specific domains, and the ability to specify your own list of CA certificates.
1 change: 0 additions & 1 deletion docs/MSC1711_certificates_FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +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`.


## FAQ

### Synapse 0.99.0 has just been released, what do I need to do right now?
Expand Down
34 changes: 34 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,40 @@ listeners:
#
#tls_private_key_path: "CONFDIR/SERVERNAME.tls.key"

# 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

# Skip federation certificate verification on the following whitelist
# of domains.
#
# This setting should only be used in very specific cases, such as
# federation over Tor hidden services and similar. For private networks
# of homeservers, you likely want to use a private CA instead.
#
# Only effective if federation_verify_certicates is `true`.
#
#federation_certificate_verification_whitelist:
# - lon.example.com
# - *.domain.com
# - *.onion

# List of custom certificate authorities for federation traffic.
#
# This setting should only normally be used within a private network of
# homeservers.
#
# Note that this list will replace those that are provided by your
# operating environment. Certificates must be in PEM format.
#
#federation_custom_ca_list:
# - myCA1.pem
# - myCA2.pem
# - myCA3.pem

# ACME support: This will configure Synapse to request a valid TLS certificate
# for your configured `server_name` via Let's Encrypt.
#
Expand Down
6 changes: 4 additions & 2 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,13 @@ def read_config(self, config):
# FIXME: federation_domain_whitelist needs sytests
self.federation_domain_whitelist = None
federation_domain_whitelist = config.get(
"federation_domain_whitelist", None
"federation_domain_whitelist", None,
)
# turn the whitelist into a hash for speed of lookup

if federation_domain_whitelist is not None:
# turn the whitelist into a hash for speed of lookup
self.federation_domain_whitelist = {}

for domain in federation_domain_whitelist:
self.federation_domain_whitelist[domain] = True

Expand Down
95 changes: 89 additions & 6 deletions synapse/config/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
from unpaddedbase64 import encode_base64

from OpenSSL import crypto
from twisted.internet._sslverify import Certificate, trustRootFromCertificates

from synapse.config._base import Config, ConfigError
from synapse.util import glob_to_regex

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -70,6 +72,53 @@ def read_config(self, config):

self.tls_fingerprints = list(self._original_tls_fingerprints)

# Whether to verify certificates on outbound federation traffic
self.federation_verify_certificates = config.get(
"federation_verify_certificates", False,
)

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

# Support globs (*) in whitelist values
self.federation_certificate_verification_whitelist = []
for entry in fed_whitelist_entries:
# Convert globs to regex
entry_regex = glob_to_regex(entry)
self.federation_certificate_verification_whitelist.append(entry_regex)

# List of custom certificate authorities for federation traffic validation
custom_ca_list = config.get(
"federation_custom_ca_list", None,
)

# Read in and parse custom CA certificates
self.federation_ca_trust_root = None
if custom_ca_list is not None:
if len(custom_ca_list) == 0:
# A trustroot cannot be generated without any CA certificates.
# Raise an error if this option has been specified without any
# corresponding certificates.
raise ConfigError("federation_custom_ca_list specified without "
"any certificate files")

certs = []
for ca_file in custom_ca_list:
logger.debug("Reading custom CA certificate file: %s", ca_file)
content = self.read_file(ca_file)

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

self.federation_ca_trust_root = trustRootFromCertificates(certs)

# This config option applies to non-federation HTTP clients
# (e.g. for talking to recaptcha, identity servers, and such)
# It should never be used in production, and is intended for
Expand Down Expand Up @@ -99,15 +148,15 @@ def is_disk_cert_valid(self, allow_self_signed=True):
try:
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
except Exception as e:
raise ConfigError("Failed to read existing certificate file %s: %s"
% (self.tls_certificate_file, e))

try:
tls_certificate = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
except Exception:
logger.exception("Failed to parse existing certificate off disk!")
raise
except Exception as e:
raise ConfigError("Failed to parse existing certificate file %s: %s"
% (self.tls_certificate_file, e))

if not allow_self_signed:
if tls_certificate.get_subject() == tls_certificate.get_issuer():
Expand Down Expand Up @@ -192,6 +241,40 @@ def default_config(self, config_dir_path, server_name, **kwargs):
#
#tls_private_key_path: "%(tls_private_key_path)s"
# 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
# Skip federation certificate verification on the following whitelist
# of domains.
#
# This setting should only be used in very specific cases, such as
# federation over Tor hidden services and similar. For private networks
# of homeservers, you likely want to use a private CA instead.
#
# Only effective if federation_verify_certicates is `true`.
#
#federation_certificate_verification_whitelist:
# - lon.example.com
# - *.domain.com
# - *.onion
# List of custom certificate authorities for federation traffic.
#
# This setting should only normally be used within a private network of
# homeservers.
#
# Note that this list will replace those that are provided by your
# operating environment. Certificates must be in PEM format.
#
#federation_custom_ca_list:
# - myCA1.pem
# - myCA2.pem
# - myCA3.pem
# ACME support: This will configure Synapse to request a valid TLS certificate
# for your configured `server_name` via Let's Encrypt.
#
Expand Down
33 changes: 27 additions & 6 deletions synapse/crypto/context_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
from zope.interface import implementer

from OpenSSL import SSL, crypto
from twisted.internet._sslverify import _defaultCurveName
from twisted.internet._sslverify import ClientTLSOptions, _defaultCurveName
from twisted.internet.abstract import isIPAddress, isIPv6Address
from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
from twisted.internet.ssl import CertificateOptions, ContextFactory
from twisted.internet.ssl import CertificateOptions, ContextFactory, platformTrust
from twisted.python.failure import Failure

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -90,7 +90,7 @@ def infoCallback(connection, where, ret):


@implementer(IOpenSSLClientConnectionCreator)
class ClientTLSOptions(object):
class ClientTLSOptionsNoVerify(object):
"""
Client creator for TLS without certificate identity verification. This is a
copy of twisted.internet._sslverify.ClientTLSOptions with the identity
Expand Down Expand Up @@ -127,9 +127,30 @@ class ClientTLSOptionsFactory(object):
to remote servers for federation."""

def __init__(self, config):
# We don't use config options yet
self._options = CertificateOptions(verify=False)
self._config = config
self._options_noverify = CertificateOptions()

# Check if we're using a custom list of a CA certificates
trust_root = config.federation_ca_trust_root
if trust_root is None:
# Use CA root certs provided by OpenSSL
trust_root = platformTrust()

self._options_verify = CertificateOptions(trustRoot=trust_root)

def get_options(self, host):
# Use _makeContext so that we get a fresh OpenSSL CTX each time.
return ClientTLSOptions(host, self._options._makeContext())

# Check if certificate verification has been enabled
should_verify = self._config.federation_verify_certificates

# Check if we've disabled certificate verification for this host
if should_verify:
for regex in self._config.federation_certificate_verification_whitelist:
if regex.match(host):
should_verify = False
break

if should_verify:
return ClientTLSOptions(host, self._options_verify._makeContext())
return ClientTLSOptionsNoVerify(host, self._options_noverify._makeContext())
2 changes: 1 addition & 1 deletion synapse/http/federation/matrix_federation_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def request(self, method, uri, headers=None, bodyProducer=None):
tls_options = None
else:
tls_options = self._tls_client_options_factory.get_options(
res.tls_server_name.decode("ascii")
res.tls_server_name.decode("ascii"),
)

# make sure that the Host header is set correctly
Expand Down
3 changes: 2 additions & 1 deletion 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 @@ -53,7 +54,7 @@ def setUp(self):

self.agent = MatrixFederationAgent(
reactor=self.reactor,
tls_client_options_factory=ClientTLSOptionsFactory(None),
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

0 comments on commit 6824ddd

Please sign in to comment.