Skip to content

Commit 86ddd8e

Browse files
richvdhphil-flex
authored andcommitted
Share SSL contexts for non-federation requests (matrix-org#7094)
Extends matrix-org#5794 etc to the SimpleHttpClient so that it also applies to non-federation requests. Fixes matrix-org#7092.
1 parent 025b37f commit 86ddd8e

File tree

7 files changed

+71
-44
lines changed

7 files changed

+71
-44
lines changed

changelog.d/7094.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve performance when making HTTPS requests to sygnal, sydent, etc, by sharing the SSL context object between connections.

synapse/crypto/context_factory.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def getContext(self):
7575

7676

7777
@implementer(IPolicyForHTTPS)
78-
class ClientTLSOptionsFactory(object):
78+
class FederationPolicyForHTTPS(object):
7979
"""Factory for Twisted SSLClientConnectionCreators that are used to make connections
8080
to remote servers for federation.
8181
@@ -103,15 +103,15 @@ def __init__(self, config):
103103
# let us do).
104104
minTLS = _TLS_VERSION_MAP[config.federation_client_minimum_tls_version]
105105

106-
self._verify_ssl = CertificateOptions(
106+
_verify_ssl = CertificateOptions(
107107
trustRoot=trust_root, insecurelyLowerMinimumTo=minTLS
108108
)
109-
self._verify_ssl_context = self._verify_ssl.getContext()
110-
self._verify_ssl_context.set_info_callback(self._context_info_cb)
109+
self._verify_ssl_context = _verify_ssl.getContext()
110+
self._verify_ssl_context.set_info_callback(_context_info_cb)
111111

112-
self._no_verify_ssl = CertificateOptions(insecurelyLowerMinimumTo=minTLS)
113-
self._no_verify_ssl_context = self._no_verify_ssl.getContext()
114-
self._no_verify_ssl_context.set_info_callback(self._context_info_cb)
112+
_no_verify_ssl = CertificateOptions(insecurelyLowerMinimumTo=minTLS)
113+
self._no_verify_ssl_context = _no_verify_ssl.getContext()
114+
self._no_verify_ssl_context.set_info_callback(_context_info_cb)
115115

116116
def get_options(self, host: bytes):
117117

@@ -136,30 +136,50 @@ def get_options(self, host: bytes):
136136

137137
return SSLClientConnectionCreator(host, ssl_context, should_verify)
138138

139-
@staticmethod
140-
def _context_info_cb(ssl_connection, where, ret):
141-
"""The 'information callback' for our openssl context object."""
142-
# we assume that the app_data on the connection object has been set to
143-
# a TLSMemoryBIOProtocol object. (This is done by SSLClientConnectionCreator)
144-
tls_protocol = ssl_connection.get_app_data()
145-
try:
146-
# ... we further assume that SSLClientConnectionCreator has set the
147-
# '_synapse_tls_verifier' attribute to a ConnectionVerifier object.
148-
tls_protocol._synapse_tls_verifier.verify_context_info_cb(
149-
ssl_connection, where
150-
)
151-
except: # noqa: E722, taken from the twisted implementation
152-
logger.exception("Error during info_callback")
153-
f = Failure()
154-
tls_protocol.failVerification(f)
155-
156139
def creatorForNetloc(self, hostname, port):
157140
"""Implements the IPolicyForHTTPS interace so that this can be passed
158141
directly to agents.
159142
"""
160143
return self.get_options(hostname)
161144

162145

146+
@implementer(IPolicyForHTTPS)
147+
class RegularPolicyForHTTPS(object):
148+
"""Factory for Twisted SSLClientConnectionCreators that are used to make connections
149+
to remote servers, for other than federation.
150+
151+
Always uses the same OpenSSL context object, which uses the default OpenSSL CA
152+
trust root.
153+
"""
154+
155+
def __init__(self):
156+
trust_root = platformTrust()
157+
self._ssl_context = CertificateOptions(trustRoot=trust_root).getContext()
158+
self._ssl_context.set_info_callback(_context_info_cb)
159+
160+
def creatorForNetloc(self, hostname, port):
161+
return SSLClientConnectionCreator(hostname, self._ssl_context, True)
162+
163+
164+
def _context_info_cb(ssl_connection, where, ret):
165+
"""The 'information callback' for our openssl context objects.
166+
167+
Note: Once this is set as the info callback on a Context object, the Context should
168+
only be used with the SSLClientConnectionCreator.
169+
"""
170+
# we assume that the app_data on the connection object has been set to
171+
# a TLSMemoryBIOProtocol object. (This is done by SSLClientConnectionCreator)
172+
tls_protocol = ssl_connection.get_app_data()
173+
try:
174+
# ... we further assume that SSLClientConnectionCreator has set the
175+
# '_synapse_tls_verifier' attribute to a ConnectionVerifier object.
176+
tls_protocol._synapse_tls_verifier.verify_context_info_cb(ssl_connection, where)
177+
except: # noqa: E722, taken from the twisted implementation
178+
logger.exception("Error during info_callback")
179+
f = Failure()
180+
tls_protocol.failVerification(f)
181+
182+
163183
@implementer(IOpenSSLClientConnectionCreator)
164184
class SSLClientConnectionCreator(object):
165185
"""Creates openssl connection objects for client connections.

synapse/http/client.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,6 @@ def __getattr__(_self, attr):
244244
pool.maxPersistentPerHost = max((100 * CACHE_SIZE_FACTOR, 5))
245245
pool.cachedConnectionTimeout = 2 * 60
246246

247-
# The default context factory in Twisted 14.0.0 (which we require) is
248-
# BrowserLikePolicyForHTTPS which will do regular cert validation
249-
# 'like a browser'
250247
self.agent = ProxyAgent(
251248
self.reactor,
252249
connectTimeout=15,

synapse/http/federation/matrix_federation_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class MatrixFederationAgent(object):
4545
Args:
4646
reactor (IReactor): twisted reactor to use for underlying requests
4747
48-
tls_client_options_factory (ClientTLSOptionsFactory|None):
48+
tls_client_options_factory (FederationPolicyForHTTPS|None):
4949
factory to use for fetching client tls options, or none to disable TLS.
5050
5151
_srv_resolver (SrvResolver|None):

synapse/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import os
2727

2828
from twisted.mail.smtp import sendmail
29-
from twisted.web.client import BrowserLikePolicyForHTTPS
3029

3130
from synapse.api.auth import Auth
3231
from synapse.api.filtering import Filtering
@@ -35,6 +34,7 @@
3534
from synapse.appservice.scheduler import ApplicationServiceScheduler
3635
from synapse.config.homeserver import HomeServerConfig
3736
from synapse.crypto import context_factory
37+
from synapse.crypto.context_factory import RegularPolicyForHTTPS
3838
from synapse.crypto.keyring import Keyring
3939
from synapse.events.builder import EventBuilderFactory
4040
from synapse.events.spamcheck import SpamChecker
@@ -310,7 +310,7 @@ def build_http_client_context_factory(self):
310310
return (
311311
InsecureInterceptableContextFactory()
312312
if self.config.use_insecure_ssl_client_just_for_testing_do_not_use
313-
else BrowserLikePolicyForHTTPS()
313+
else RegularPolicyForHTTPS()
314314
)
315315

316316
def build_simple_http_client(self):
@@ -420,7 +420,7 @@ def build_pusherpool(self):
420420
return PusherPool(self)
421421

422422
def build_http_client(self):
423-
tls_client_options_factory = context_factory.ClientTLSOptionsFactory(
423+
tls_client_options_factory = context_factory.FederationPolicyForHTTPS(
424424
self.config
425425
)
426426
return MatrixFederationHttpClient(self, tls_client_options_factory)

tests/config/test_tls.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from synapse.config._base import Config, RootConfig
2525
from synapse.config.tls import ConfigError, TlsConfig
26-
from synapse.crypto.context_factory import ClientTLSOptionsFactory
26+
from synapse.crypto.context_factory import FederationPolicyForHTTPS
2727

2828
from tests.unittest import TestCase
2929

@@ -180,12 +180,13 @@ def test_tls_client_minimum_set_passed_through_1_2(self):
180180
t = TestConfig()
181181
t.read_config(config, config_dir_path="", data_dir_path="")
182182

183-
cf = ClientTLSOptionsFactory(t)
183+
cf = FederationPolicyForHTTPS(t)
184+
options = _get_ssl_context_options(cf._verify_ssl_context)
184185

185186
# The context has had NO_TLSv1_1 and NO_TLSv1_0 set, but not NO_TLSv1_2
186-
self.assertNotEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1, 0)
187-
self.assertNotEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1_1, 0)
188-
self.assertEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1_2, 0)
187+
self.assertNotEqual(options & SSL.OP_NO_TLSv1, 0)
188+
self.assertNotEqual(options & SSL.OP_NO_TLSv1_1, 0)
189+
self.assertEqual(options & SSL.OP_NO_TLSv1_2, 0)
189190

190191
def test_tls_client_minimum_set_passed_through_1_0(self):
191192
"""
@@ -195,12 +196,13 @@ def test_tls_client_minimum_set_passed_through_1_0(self):
195196
t = TestConfig()
196197
t.read_config(config, config_dir_path="", data_dir_path="")
197198

198-
cf = ClientTLSOptionsFactory(t)
199+
cf = FederationPolicyForHTTPS(t)
200+
options = _get_ssl_context_options(cf._verify_ssl_context)
199201

200202
# The context has not had any of the NO_TLS set.
201-
self.assertEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1, 0)
202-
self.assertEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1_1, 0)
203-
self.assertEqual(cf._verify_ssl._options & SSL.OP_NO_TLSv1_2, 0)
203+
self.assertEqual(options & SSL.OP_NO_TLSv1, 0)
204+
self.assertEqual(options & SSL.OP_NO_TLSv1_1, 0)
205+
self.assertEqual(options & SSL.OP_NO_TLSv1_2, 0)
204206

205207
def test_acme_disabled_in_generated_config_no_acme_domain_provied(self):
206208
"""
@@ -273,7 +275,7 @@ def test_whitelist_idna_result(self):
273275
t = TestConfig()
274276
t.read_config(config, config_dir_path="", data_dir_path="")
275277

276-
cf = ClientTLSOptionsFactory(t)
278+
cf = FederationPolicyForHTTPS(t)
277279

278280
# Not in the whitelist
279281
opts = cf.get_options(b"notexample.com")
@@ -282,3 +284,10 @@ def test_whitelist_idna_result(self):
282284
# Caught by the wildcard
283285
opts = cf.get_options(idna.encode("テスト.ドメイン.テスト"))
284286
self.assertFalse(opts._verifier._verify_certs)
287+
288+
289+
def _get_ssl_context_options(ssl_context: SSL.Context) -> int:
290+
"""get the options bits from an openssl context object"""
291+
# the OpenSSL.SSL.Context wrapper doesn't expose get_options, so we have to
292+
# use the low-level interface
293+
return SSL._lib.SSL_CTX_get_options(ssl_context._context)

tests/http/federation/test_matrix_federation_agent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from twisted.web.iweb import IPolicyForHTTPS
3232

3333
from synapse.config.homeserver import HomeServerConfig
34-
from synapse.crypto.context_factory import ClientTLSOptionsFactory
34+
from synapse.crypto.context_factory import FederationPolicyForHTTPS
3535
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
3636
from synapse.http.federation.srv_resolver import Server
3737
from synapse.http.federation.well_known_resolver import (
@@ -79,7 +79,7 @@ def setUp(self):
7979
self._config = config = HomeServerConfig()
8080
config.parse_config_dict(config_dict, "", "")
8181

82-
self.tls_factory = ClientTLSOptionsFactory(config)
82+
self.tls_factory = FederationPolicyForHTTPS(config)
8383

8484
self.well_known_cache = TTLCache("test_cache", timer=self.reactor.seconds)
8585
self.had_well_known_cache = TTLCache("test_cache", timer=self.reactor.seconds)
@@ -715,7 +715,7 @@ def test_get_well_known_unsigned_cert(self):
715715
config = default_config("test", parse=True)
716716

717717
# Build a new agent and WellKnownResolver with a different tls factory
718-
tls_factory = ClientTLSOptionsFactory(config)
718+
tls_factory = FederationPolicyForHTTPS(config)
719719
agent = MatrixFederationAgent(
720720
reactor=self.reactor,
721721
tls_client_options_factory=tls_factory,

0 commit comments

Comments
 (0)