Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

273 строки
10 KiB

  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2019 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. from service_identity import VerificationError
  17. from service_identity.pyopenssl import verify_hostname, verify_ip_address
  18. from zope.interface import implementer
  19. from OpenSSL import SSL, crypto
  20. from twisted.internet._sslverify import _defaultCurveName
  21. from twisted.internet.abstract import isIPAddress, isIPv6Address
  22. from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
  23. from twisted.internet.ssl import (
  24. CertificateOptions,
  25. ContextFactory,
  26. TLSVersion,
  27. platformTrust,
  28. )
  29. from twisted.protocols.tls import TLSMemoryBIOProtocol
  30. from twisted.python.failure import Failure
  31. from twisted.web.iweb import IPolicyForHTTPS
  32. from synapse.config.homeserver import HomeServerConfig
  33. logger = logging.getLogger(__name__)
  34. _TLS_VERSION_MAP = {
  35. "1": TLSVersion.TLSv1_0,
  36. "1.1": TLSVersion.TLSv1_1,
  37. "1.2": TLSVersion.TLSv1_2,
  38. "1.3": TLSVersion.TLSv1_3,
  39. }
  40. class ServerContextFactory(ContextFactory):
  41. """Factory for PyOpenSSL SSL contexts that are used to handle incoming
  42. connections.
  43. TODO: replace this with an implementation of IOpenSSLServerConnectionCreator,
  44. per https://github.com/matrix-org/synapse/issues/1691
  45. """
  46. def __init__(self, config: HomeServerConfig):
  47. # TODO: once pyOpenSSL exposes TLS_METHOD and SSL_CTX_set_min_proto_version,
  48. # switch to those (see https://github.com/pyca/cryptography/issues/5379).
  49. #
  50. # note that, despite the confusing name, SSLv23_METHOD does *not* enforce SSLv2
  51. # or v3, but is a synonym for TLS_METHOD, which allows the client and server
  52. # to negotiate an appropriate version of TLS constrained by the version options
  53. # set with context.set_options.
  54. #
  55. self._context = SSL.Context(SSL.SSLv23_METHOD)
  56. self.configure_context(self._context, config)
  57. @staticmethod
  58. def configure_context(context: SSL.Context, config: HomeServerConfig) -> None:
  59. try:
  60. _ecCurve = crypto.get_elliptic_curve(_defaultCurveName)
  61. context.set_tmp_ecdh(_ecCurve)
  62. except Exception:
  63. logger.exception("Failed to enable elliptic curve for TLS")
  64. context.set_options(
  65. SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3 | SSL.OP_NO_TLSv1 | SSL.OP_NO_TLSv1_1
  66. )
  67. context.use_certificate_chain_file(config.tls.tls_certificate_file)
  68. assert config.tls.tls_private_key is not None
  69. context.use_privatekey(config.tls.tls_private_key)
  70. # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
  71. context.set_cipher_list(
  72. b"ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES256:ECDH+AES128:!aNULL:!SHA1:!AESCCM"
  73. )
  74. def getContext(self) -> SSL.Context:
  75. return self._context
  76. @implementer(IPolicyForHTTPS)
  77. class FederationPolicyForHTTPS:
  78. """Factory for Twisted SSLClientConnectionCreators that are used to make connections
  79. to remote servers for federation.
  80. Uses one of two OpenSSL context objects for all connections, depending on whether
  81. we should do SSL certificate verification.
  82. get_options decides whether we should do SSL certificate verification and
  83. constructs an SSLClientConnectionCreator factory accordingly.
  84. """
  85. def __init__(self, config: HomeServerConfig):
  86. self._config = config
  87. # Check if we're using a custom list of a CA certificates
  88. trust_root = config.tls.federation_ca_trust_root
  89. if trust_root is None:
  90. # Use CA root certs provided by OpenSSL
  91. trust_root = platformTrust()
  92. # "insecurelyLowerMinimumTo" is the argument that will go lower than
  93. # Twisted's default, which is why it is marked as "insecure" (since
  94. # Twisted's defaults are reasonably secure). But, since Twisted is
  95. # moving to TLS 1.2 by default, we want to respect the config option if
  96. # it is set to 1.0 (which the alternate option, raiseMinimumTo, will not
  97. # let us do).
  98. minTLS = _TLS_VERSION_MAP[config.tls.federation_client_minimum_tls_version]
  99. _verify_ssl = CertificateOptions(
  100. trustRoot=trust_root, insecurelyLowerMinimumTo=minTLS
  101. )
  102. self._verify_ssl_context = _verify_ssl.getContext()
  103. self._verify_ssl_context.set_info_callback(_context_info_cb)
  104. _no_verify_ssl = CertificateOptions(insecurelyLowerMinimumTo=minTLS)
  105. self._no_verify_ssl_context = _no_verify_ssl.getContext()
  106. self._no_verify_ssl_context.set_info_callback(_context_info_cb)
  107. self._should_verify = self._config.tls.federation_verify_certificates
  108. self._federation_certificate_verification_whitelist = (
  109. self._config.tls.federation_certificate_verification_whitelist
  110. )
  111. def get_options(self, host: bytes) -> IOpenSSLClientConnectionCreator:
  112. # IPolicyForHTTPS.get_options takes bytes, but we want to compare
  113. # against the str whitelist. The hostnames in the whitelist are already
  114. # IDNA-encoded like the hosts will be here.
  115. ascii_host = host.decode("ascii")
  116. # Check if certificate verification has been enabled
  117. should_verify = self._should_verify
  118. # Check if we've disabled certificate verification for this host
  119. if self._should_verify:
  120. for regex in self._federation_certificate_verification_whitelist:
  121. if regex.match(ascii_host):
  122. should_verify = False
  123. break
  124. ssl_context = (
  125. self._verify_ssl_context if should_verify else self._no_verify_ssl_context
  126. )
  127. return SSLClientConnectionCreator(host, ssl_context, should_verify)
  128. def creatorForNetloc(
  129. self, hostname: bytes, port: int
  130. ) -> IOpenSSLClientConnectionCreator:
  131. """Implements the IPolicyForHTTPS interface so that this can be passed
  132. directly to agents.
  133. """
  134. return self.get_options(hostname)
  135. @implementer(IPolicyForHTTPS)
  136. class RegularPolicyForHTTPS:
  137. """Factory for Twisted SSLClientConnectionCreators that are used to make connections
  138. to remote servers, for other than federation.
  139. Always uses the same OpenSSL context object, which uses the default OpenSSL CA
  140. trust root.
  141. """
  142. def __init__(self) -> None:
  143. trust_root = platformTrust()
  144. self._ssl_context = CertificateOptions(trustRoot=trust_root).getContext()
  145. self._ssl_context.set_info_callback(_context_info_cb)
  146. def creatorForNetloc(
  147. self, hostname: bytes, port: int
  148. ) -> IOpenSSLClientConnectionCreator:
  149. return SSLClientConnectionCreator(hostname, self._ssl_context, True)
  150. def _context_info_cb(ssl_connection: SSL.Connection, where: int, ret: int) -> None:
  151. """The 'information callback' for our openssl context objects.
  152. Note: Once this is set as the info callback on a Context object, the Context should
  153. only be used with the SSLClientConnectionCreator.
  154. """
  155. # we assume that the app_data on the connection object has been set to
  156. # a TLSMemoryBIOProtocol object. (This is done by SSLClientConnectionCreator)
  157. tls_protocol = ssl_connection.get_app_data()
  158. try:
  159. # ... we further assume that SSLClientConnectionCreator has set the
  160. # '_synapse_tls_verifier' attribute to a ConnectionVerifier object.
  161. tls_protocol._synapse_tls_verifier.verify_context_info_cb(ssl_connection, where)
  162. except BaseException: # taken from the twisted implementation
  163. logger.exception("Error during info_callback")
  164. f = Failure()
  165. tls_protocol.failVerification(f)
  166. @implementer(IOpenSSLClientConnectionCreator)
  167. class SSLClientConnectionCreator:
  168. """Creates openssl connection objects for client connections.
  169. Replaces twisted.internet.ssl.ClientTLSOptions
  170. """
  171. def __init__(self, hostname: bytes, ctx: SSL.Context, verify_certs: bool):
  172. self._ctx = ctx
  173. self._verifier = ConnectionVerifier(hostname, verify_certs)
  174. def clientConnectionForTLS(
  175. self, tls_protocol: TLSMemoryBIOProtocol
  176. ) -> SSL.Connection:
  177. context = self._ctx
  178. connection = SSL.Connection(context, None)
  179. # as per twisted.internet.ssl.ClientTLSOptions, we set the application
  180. # data to our TLSMemoryBIOProtocol...
  181. connection.set_app_data(tls_protocol)
  182. # ... and we also gut-wrench a '_synapse_tls_verifier' attribute into the
  183. # tls_protocol so that the SSL context's info callback has something to
  184. # call to do the cert verification.
  185. tls_protocol._synapse_tls_verifier = self._verifier # type: ignore[attr-defined]
  186. return connection
  187. class ConnectionVerifier:
  188. """Set the SNI, and do cert verification
  189. This is a thing which is attached to the TLSMemoryBIOProtocol, and is called by
  190. the ssl context's info callback.
  191. """
  192. # This code is based on twisted.internet.ssl.ClientTLSOptions.
  193. def __init__(self, hostname: bytes, verify_certs: bool):
  194. self._verify_certs = verify_certs
  195. _decoded = hostname.decode("ascii")
  196. if isIPAddress(_decoded) or isIPv6Address(_decoded):
  197. self._is_ip_address = True
  198. else:
  199. self._is_ip_address = False
  200. self._hostnameBytes = hostname
  201. self._hostnameASCII = self._hostnameBytes.decode("ascii")
  202. def verify_context_info_cb(
  203. self, ssl_connection: SSL.Connection, where: int
  204. ) -> None:
  205. if where & SSL.SSL_CB_HANDSHAKE_START and not self._is_ip_address:
  206. ssl_connection.set_tlsext_host_name(self._hostnameBytes)
  207. if where & SSL.SSL_CB_HANDSHAKE_DONE and self._verify_certs:
  208. try:
  209. if self._is_ip_address:
  210. verify_ip_address(ssl_connection, self._hostnameASCII)
  211. else:
  212. verify_hostname(ssl_connection, self._hostnameASCII)
  213. except VerificationError:
  214. f = Failure()
  215. tls_protocol = ssl_connection.get_app_data()
  216. tls_protocol.failVerification(f)