You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

204 lines
7.6 KiB

  1. # Copyright 2019 New Vector Ltd
  2. # Copyright 2019 Matrix.org Foundation C.I.C.
  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 typing import cast
  16. import idna
  17. from OpenSSL import SSL
  18. from synapse.config._base import Config, RootConfig
  19. from synapse.config.homeserver import HomeServerConfig
  20. from synapse.config.tls import ConfigError, TlsConfig
  21. from synapse.crypto.context_factory import (
  22. FederationPolicyForHTTPS,
  23. SSLClientConnectionCreator,
  24. )
  25. from synapse.types import JsonDict
  26. from tests.unittest import TestCase
  27. class FakeServer(Config):
  28. section = "server"
  29. def has_tls_listener(self) -> bool:
  30. return False
  31. class TestConfig(RootConfig):
  32. config_classes = [FakeServer, TlsConfig]
  33. class TLSConfigTests(TestCase):
  34. def test_tls_client_minimum_default(self) -> None:
  35. """
  36. The default client TLS version is 1.0.
  37. """
  38. config: JsonDict = {}
  39. t = TestConfig()
  40. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  41. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1")
  42. def test_tls_client_minimum_set(self) -> None:
  43. """
  44. The default client TLS version can be set to 1.0, 1.1, and 1.2.
  45. """
  46. config: JsonDict = {"federation_client_minimum_tls_version": 1}
  47. t = TestConfig()
  48. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  49. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1")
  50. config = {"federation_client_minimum_tls_version": 1.1}
  51. t = TestConfig()
  52. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  53. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1.1")
  54. config = {"federation_client_minimum_tls_version": 1.2}
  55. t = TestConfig()
  56. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  57. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1.2")
  58. # Also test a string version
  59. config = {"federation_client_minimum_tls_version": "1"}
  60. t = TestConfig()
  61. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  62. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1")
  63. config = {"federation_client_minimum_tls_version": "1.2"}
  64. t = TestConfig()
  65. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  66. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1.2")
  67. def test_tls_client_minimum_1_point_3_missing(self) -> None:
  68. """
  69. If TLS 1.3 support is missing and it's configured, it will raise a
  70. ConfigError.
  71. """
  72. # thanks i hate it
  73. if hasattr(SSL, "OP_NO_TLSv1_3"):
  74. OP_NO_TLSv1_3 = SSL.OP_NO_TLSv1_3
  75. delattr(SSL, "OP_NO_TLSv1_3")
  76. self.addCleanup(setattr, SSL, "SSL.OP_NO_TLSv1_3", OP_NO_TLSv1_3)
  77. assert not hasattr(SSL, "OP_NO_TLSv1_3")
  78. config: JsonDict = {"federation_client_minimum_tls_version": 1.3}
  79. t = TestConfig()
  80. with self.assertRaises(ConfigError) as e:
  81. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  82. self.assertEqual(
  83. e.exception.args[0],
  84. (
  85. "federation_client_minimum_tls_version cannot be 1.3, "
  86. "your OpenSSL does not support it"
  87. ),
  88. )
  89. def test_tls_client_minimum_1_point_3_exists(self) -> None:
  90. """
  91. If TLS 1.3 support exists and it's configured, it will be settable.
  92. """
  93. # thanks i hate it, still
  94. if not hasattr(SSL, "OP_NO_TLSv1_3"):
  95. SSL.OP_NO_TLSv1_3 = 0x00
  96. self.addCleanup(lambda: delattr(SSL, "OP_NO_TLSv1_3"))
  97. assert hasattr(SSL, "OP_NO_TLSv1_3")
  98. config: JsonDict = {"federation_client_minimum_tls_version": 1.3}
  99. t = TestConfig()
  100. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  101. self.assertEqual(t.tls.federation_client_minimum_tls_version, "1.3")
  102. def test_tls_client_minimum_set_passed_through_1_2(self) -> None:
  103. """
  104. The configured TLS version is correctly configured by the ContextFactory.
  105. """
  106. config: JsonDict = {"federation_client_minimum_tls_version": 1.2}
  107. t = TestConfig()
  108. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  109. cf = FederationPolicyForHTTPS(cast(HomeServerConfig, t))
  110. options = _get_ssl_context_options(cf._verify_ssl_context)
  111. # The context has had NO_TLSv1_1 and NO_TLSv1_0 set, but not NO_TLSv1_2
  112. self.assertNotEqual(options & SSL.OP_NO_TLSv1, 0)
  113. self.assertNotEqual(options & SSL.OP_NO_TLSv1_1, 0)
  114. self.assertEqual(options & SSL.OP_NO_TLSv1_2, 0)
  115. def test_tls_client_minimum_set_passed_through_1_0(self) -> None:
  116. """
  117. The configured TLS version is correctly configured by the ContextFactory.
  118. """
  119. config: JsonDict = {"federation_client_minimum_tls_version": 1}
  120. t = TestConfig()
  121. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  122. cf = FederationPolicyForHTTPS(cast(HomeServerConfig, t))
  123. options = _get_ssl_context_options(cf._verify_ssl_context)
  124. # The context has not had any of the NO_TLS set.
  125. self.assertEqual(options & SSL.OP_NO_TLSv1, 0)
  126. self.assertEqual(options & SSL.OP_NO_TLSv1_1, 0)
  127. self.assertEqual(options & SSL.OP_NO_TLSv1_2, 0)
  128. def test_whitelist_idna_failure(self) -> None:
  129. """
  130. The federation certificate whitelist will not allow IDNA domain names.
  131. """
  132. config: JsonDict = {
  133. "federation_certificate_verification_whitelist": [
  134. "example.com",
  135. "*.ドメイン.テスト",
  136. ]
  137. }
  138. t = TestConfig()
  139. e = self.assertRaises(
  140. ConfigError, t.tls.read_config, config, config_dir_path="", data_dir_path=""
  141. )
  142. self.assertIn("IDNA domain names", str(e))
  143. def test_whitelist_idna_result(self) -> None:
  144. """
  145. The federation certificate whitelist will match on IDNA encoded names.
  146. """
  147. config: JsonDict = {
  148. "federation_certificate_verification_whitelist": [
  149. "example.com",
  150. "*.xn--eckwd4c7c.xn--zckzah",
  151. ]
  152. }
  153. t = TestConfig()
  154. t.tls.read_config(config, config_dir_path="", data_dir_path="")
  155. cf = FederationPolicyForHTTPS(cast(HomeServerConfig, t))
  156. # Not in the whitelist
  157. opts = cf.get_options(b"notexample.com")
  158. assert isinstance(opts, SSLClientConnectionCreator)
  159. self.assertTrue(opts._verifier._verify_certs)
  160. # Caught by the wildcard
  161. opts = cf.get_options(idna.encode("テスト.ドメイン.テスト"))
  162. assert isinstance(opts, SSLClientConnectionCreator)
  163. self.assertFalse(opts._verifier._verify_certs)
  164. def _get_ssl_context_options(ssl_context: SSL.Context) -> int:
  165. """get the options bits from an openssl context object"""
  166. # the OpenSSL.SSL.Context wrapper doesn't expose get_options, so we have to
  167. # use the low-level interface
  168. return SSL._lib.SSL_CTX_get_options(ssl_context._context) # type: ignore[attr-defined]