federation_tls_options.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. # -*- coding: utf-8 -*-
  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. from OpenSSL import SSL
  16. from zope.interface import implementer
  17. from twisted.internet import ssl
  18. from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
  19. from twisted.internet.abstract import isIPAddress, isIPv6Address
  20. from twisted.internet._sslverify import ClientTLSOptions
  21. def _tolerateErrors(wrapped):
  22. """
  23. Wrap up an info_callback for pyOpenSSL so that if something goes wrong
  24. the error is immediately logged and the connection is dropped if possible.
  25. This is a copy of twisted.internet._sslverify._tolerateErrors. For
  26. documentation, see the twisted documentation.
  27. """
  28. def infoCallback(connection, where, ret):
  29. try:
  30. return wrapped(connection, where, ret)
  31. except: # noqa: E722, taken from the twisted implementation
  32. f = Failure()
  33. logger.exception("Error during info_callback")
  34. connection.get_app_data().failVerification(f)
  35. return infoCallback
  36. def _idnaBytes(text):
  37. """
  38. Convert some text typed by a human into some ASCII bytes. This is a
  39. copy of twisted.internet._idna._idnaBytes. For documentation, see the
  40. twisted documentation.
  41. """
  42. try:
  43. import idna
  44. except ImportError:
  45. return text.encode("idna")
  46. else:
  47. return idna.encode(text)
  48. @implementer(IOpenSSLClientConnectionCreator)
  49. class ClientTLSOptions(object):
  50. """
  51. Client creator for TLS without certificate identity verification. This is a
  52. copy of twisted.internet._sslverify.ClientTLSOptions with the identity
  53. verification left out. For documentation, see the twisted documentation.
  54. """
  55. def __init__(self, hostname, ctx):
  56. self._ctx = ctx
  57. if isIPAddress(hostname) or isIPv6Address(hostname):
  58. self._hostnameBytes = hostname.encode('ascii')
  59. self._sendSNI = False
  60. else:
  61. self._hostnameBytes = _idnaBytes(hostname)
  62. self._sendSNI = True
  63. ctx.set_info_callback(_tolerateErrors(self._identityVerifyingInfoCallback))
  64. def clientConnectionForTLS(self, tlsProtocol):
  65. context = self._ctx
  66. connection = SSL.Connection(context, None)
  67. connection.set_app_data(tlsProtocol)
  68. return connection
  69. def _identityVerifyingInfoCallback(self, connection, where, ret):
  70. # Literal IPv4 and IPv6 addresses are not permitted
  71. # as host names according to the RFCs
  72. if where & SSL.SSL_CB_HANDSHAKE_START and self._sendSNI:
  73. connection.set_tlsext_host_name(self._hostnameBytes)
  74. class ClientTLSOptionsFactory(object):
  75. """Factory for Twisted ClientTLSOptions that are used to make connections
  76. to remote servers for federation."""
  77. def __init__(self, config):
  78. verify_requests = config.getboolean("http", "federation.verifycerts")
  79. if verify_requests:
  80. self._options = ssl.CertificateOptions(trustRoot=ssl.platformTrust())
  81. else:
  82. self._options = ssl.CertificateOptions()
  83. def get_options(self, host):
  84. # Use _makeContext so that we get a fresh OpenSSL CTX each time.
  85. return ClientTLSOptions(host, self._options._makeContext())