Alternative TLS implementation in Go
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

294 líneas
11 KiB

  1. #!/usr/bin/env python2
  2. import docker
  3. import unittest
  4. import re
  5. import time
  6. # Regex patterns used for testing
  7. # Checks if TLS 1.3 was negotiated
  8. RE_PATTERN_HELLO_TLS_13_NORESUME = "^.*Hello TLS 1.3 \(draft .*\) _o/$|^.*Hello TLS 1.3 _o/$"
  9. # Checks if TLS 1.3 was resumed
  10. RE_PATTERN_HELLO_TLS_13_RESUME = "Hello TLS 1.3 \[resumed\] _o/"
  11. # Checks if 0-RTT was used and NOT confirmed
  12. RE_PATTERN_HELLO_0RTT = "^.*Hello TLS 1.3 .*\[resumed\] \[0-RTT\] _o/$"
  13. # Checks if 0-RTT was used and confirmed
  14. RE_PATTERN_HELLO_0RTT_CONFIRMED = "^.*Hello TLS 1.3 .*\[resumed\] \[0-RTT confirmed\] _o/$"
  15. # ALPN
  16. RE_PATTERN_ALPN = "ALPN protocol: npn_proto$"
  17. # Successful TLS establishement from TRIS
  18. RE_TRIS_ALL_PASSED = ".*All handshakes passed.*"
  19. # TLS handshake from BoringSSL with SIDH/P503-X25519
  20. RE_BORINGSSL_P503 = "ECDHE curve: x25519sidh503"
  21. class Docker(object):
  22. ''' Utility class used for starting/stoping servers and clients during tests'''
  23. def __init__(self):
  24. self.d = docker.from_env()
  25. def get_ip(self, server):
  26. tris_localserver_container = self.d.containers.get(server)
  27. return tris_localserver_container.attrs['NetworkSettings']['IPAddress']
  28. def run_client(self, image_name, cmd):
  29. ''' Runs client and returns tuple (status_code, logs) '''
  30. c = self.d.containers.create(image=image_name, command=cmd)
  31. c.start()
  32. res = c.wait()
  33. ret = c.logs()
  34. c.remove()
  35. return (res['StatusCode'], ret)
  36. def run_server(self, image_name, cmd=None, ports=None, entrypoint=None):
  37. ''' Starts server and returns docker container '''
  38. c = self.d.containers.create(image=image_name, detach=True, command=cmd, ports=ports, entrypoint=entrypoint)
  39. c.start()
  40. # TODO: maybe can be done better?
  41. time.sleep(3)
  42. return c
  43. class RegexSelfTest(unittest.TestCase):
  44. ''' Ensures that those regexe's actually work '''
  45. LINE_HELLO_TLS ="\nsomestuff\nHello TLS 1.3 _o/\nsomestuff"
  46. LINE_HELLO_DRAFT_TLS="\nsomestuff\nHello TLS 1.3 (draft 23) _o/\nsomestuff"
  47. LINE_HELLO_RESUMED ="\nsomestuff\nHello TLS 1.3 [resumed] _o/\nsomestuff"
  48. LINE_HELLO_MIXED ="\nsomestuff\nHello TLS 1.3 (draft 23) _o/\nHello TLS 1.3 (draft 23) [resumed] _o/\nsomestuff"
  49. LINE_HELLO_TLS_12 ="\nsomestuff\nHello TLS 1.2 (draft 23) [resumed] _o/\nsomestuff"
  50. LINE_HELLO_TLS_13_0RTT="\nsomestuff\nHello TLS 1.3 (draft 23) [resumed] [0-RTT] _o/\nsomestuff"
  51. LINE_HELLO_TLS_13_0RTT_CONFIRMED="\nsomestuff\nHello TLS 1.3 (draft 23) [resumed] [0-RTT confirmed] _o/\nsomestuff"
  52. def test_regexes(self):
  53. self.assertIsNotNone(
  54. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_TLS, re.MULTILINE))
  55. self.assertIsNotNone(
  56. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_DRAFT_TLS, re.MULTILINE))
  57. self.assertIsNotNone(
  58. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_RESUMED, re.MULTILINE))
  59. self.assertIsNotNone(
  60. re.search(RE_PATTERN_HELLO_0RTT, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
  61. self.assertIsNotNone(
  62. re.search(RE_PATTERN_HELLO_0RTT_CONFIRMED, RegexSelfTest.LINE_HELLO_TLS_13_0RTT_CONFIRMED, re.MULTILINE))
  63. # negative cases
  64. # expects 1.3, but 1.2 received
  65. self.assertIsNone(
  66. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_TLS_12, re.MULTILINE))
  67. # expects 0-RTT
  68. self.assertIsNone(
  69. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
  70. # expectes 0-RTT confirmed
  71. self.assertIsNone(
  72. re.search(RE_PATTERN_HELLO_0RTT, RegexSelfTest.LINE_HELLO_TLS_13_0RTT_CONFIRMED, re.MULTILINE))
  73. # expects resume without 0-RTT
  74. self.assertIsNone(
  75. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
  76. class InteropServer(object):
  77. ''' Instantiates TRIS as a server '''
  78. TRIS_SERVER_NAME = "tris-localserver"
  79. @classmethod
  80. def setUpClass(self):
  81. self.d = Docker()
  82. self.server = self.d.run_server(self.TRIS_SERVER_NAME)
  83. @classmethod
  84. def tearDownClass(self):
  85. self.server.kill()
  86. self.server.remove()
  87. @property
  88. def server_ip(self):
  89. return self.d.get_ip(self.server.name)
  90. # Mixins for testing server functionality
  91. class ServerNominalMixin(object):
  92. ''' Nominal tests for TLS 1.3 - client tries to perform handshake with server '''
  93. def test_rsa(self):
  94. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":"+'1443')
  95. self.assertTrue(res[0] == 0)
  96. # Check there was TLS hello without resume
  97. self.assertIsNotNone(
  98. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
  99. # Check there was TLS hello with resume
  100. self.assertIsNotNone(
  101. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
  102. def test_ecdsa(self):
  103. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":"+'2443')
  104. self.assertTrue(res[0] == 0)
  105. # Check there was TLS hello without resume
  106. self.assertIsNotNone(
  107. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
  108. # Check there was TLS hello with resume
  109. self.assertIsNotNone(
  110. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
  111. class ServerClientAuthMixin(object):
  112. ''' Client authentication testing '''
  113. def test_client_auth(self):
  114. args = ''.join([self.server_ip+':6443',' -key client_rsa.key -cert client_rsa.crt -debug'])
  115. res = self.d.run_client(self.CLIENT_NAME, args)
  116. self.assertEqual(res[0], 0)
  117. # Check there was TLS hello without resume
  118. self.assertIsNotNone(
  119. re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
  120. # Check there was TLS hello with resume
  121. self.assertIsNotNone(
  122. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
  123. class ClientNominalMixin(object):
  124. def test_rsa(self):
  125. res = self.d.run_client(self.CLIENT_NAME, '-ecdsa=false '+self.server_ip+":1443")
  126. self.assertEqual(res[0], 0)
  127. def test_ecdsa(self):
  128. res = self.d.run_client(self.CLIENT_NAME, '-rsa=false '+self.server_ip+":2443")
  129. self.assertEqual(res[0], 0)
  130. class ClientClientAuthMixin(object):
  131. ''' Client authentication testing - tris on client side '''
  132. def test_client_auth(self):
  133. res = self.d.run_client('tris-testclient', '-rsa=false -cliauth '+self.server_ip+":6443")
  134. self.assertTrue(res[0] == 0)
  135. class ServerZeroRttMixin(object):
  136. ''' Zero RTT testing '''
  137. def test_zero_rtt(self):
  138. # rejecting 0-RTT
  139. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":3443")
  140. self.assertEqual(res[0], 0)
  141. self.assertIsNotNone(
  142. re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
  143. self.assertIsNone(
  144. re.search(RE_PATTERN_HELLO_0RTT, res[1], re.MULTILINE))
  145. # accepting 0-RTT
  146. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":4443")
  147. self.assertEqual(res[0], 0)
  148. self.assertIsNotNone(
  149. re.search(RE_PATTERN_HELLO_0RTT, res[1], re.MULTILINE))
  150. # confirming 0-RTT
  151. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":5443")
  152. self.assertEqual(res[0], 0)
  153. self.assertIsNotNone(
  154. re.search(RE_PATTERN_HELLO_0RTT_CONFIRMED, res[1], re.MULTILINE))
  155. class InteropClient(object):
  156. ''' Instantiates TRIS as a client '''
  157. CLIENT_NAME = "tris-testclient"
  158. @classmethod
  159. def setUpClass(self):
  160. self.d = Docker()
  161. self.server = self.d.run_server(
  162. self.SERVER_NAME,
  163. ports={ '1443/tcp': 1443, '2443/tcp': 2443, '6443/tcp': 6443, '7443/tcp': 7443},
  164. entrypoint="/server.sh")
  165. @classmethod
  166. def tearDownClass(self):
  167. self.server.kill()
  168. self.server.remove()
  169. @property
  170. def server_ip(self):
  171. return self.d.get_ip(self.server.name)
  172. # Actual test definition
  173. # TRIS as a server, BoringSSL as a client
  174. class InteropServer_BoringSSL(InteropServer, ServerNominalMixin, ServerClientAuthMixin, unittest.TestCase):
  175. CLIENT_NAME = "tls-tris:boring"
  176. def test_ALPN(self):
  177. '''
  178. Checks wether ALPN is sent back by tris server in EncryptedExtensions in case of TLS 1.3. The
  179. ALPN protocol is set to 'npn_proto', which is hardcoded in TRIS test server.
  180. '''
  181. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":1443 "+'-alpn-protos npn_proto')
  182. self.assertEqual(res[0], 0)
  183. self.assertIsNotNone(re.search(RE_PATTERN_ALPN, res[1], re.MULTILINE))
  184. def test_SIDH(self):
  185. '''
  186. Connects to TRIS server listening on 7443 and tries to perform key agreement with SIDH/P503-X25519
  187. '''
  188. res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":7443 "+'-curves x25519sidh503')
  189. self.assertEqual(res[0], 0)
  190. self.assertIsNotNone(re.search(RE_BORINGSSL_P503, res[1], re.MULTILINE))
  191. self.assertIsNotNone(re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
  192. # PicoTLS doesn't seem to implement draft-23 correctly. It will
  193. # be enabled when draft-28 is implemented.
  194. # class InteropServer_PicoTLS(
  195. # InteropServer,
  196. # ServerNominalMixin,
  197. # ServerZeroRttMixin,
  198. # unittest.TestCase
  199. # ): CLIENT_NAME = "tls-tris:picotls"
  200. class InteropServer_NSS(
  201. InteropServer,
  202. ServerNominalMixin,
  203. ServerZeroRttMixin,
  204. unittest.TestCase
  205. ): CLIENT_NAME = "tls-tris:tstclnt"
  206. # TRIS as a client, BoringSSL as a server
  207. class InteropClient_BoringSSL(InteropClient, ClientNominalMixin, ClientClientAuthMixin, unittest.TestCase):
  208. SERVER_NAME = "boring-localserver"
  209. def test_SIDH(self):
  210. '''
  211. Connects to BoringSSL server listening on 7443 and tries to perform key agreement with SIDH/P503-X25519
  212. '''
  213. res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -qr SIDH-P503-X25519 ' + self.server_ip+":7443")
  214. self.assertEqual(res[0], 0)
  215. self.assertIsNotNone(re.search(RE_TRIS_ALL_PASSED, res[1], re.MULTILINE))
  216. class InteropClient_NSS(
  217. InteropClient,
  218. ClientNominalMixin,
  219. unittest.TestCase
  220. ): SERVER_NAME = "tstclnt-localserver"
  221. # TRIS as a client
  222. class InteropServer_TRIS(ClientNominalMixin, InteropServer, unittest.TestCase):
  223. CLIENT_NAME = 'tris-testclient'
  224. def test_client_auth(self):
  225. # I need to block TLS v1.2 as test server needs some rework
  226. res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=false -cliauth '+self.server_ip+":6443")
  227. self.assertEqual(res[0], 0)
  228. def test_SIDH(self):
  229. res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -qr SIDH-P503-X25519 '+self.server_ip+":7443")
  230. self.assertEqual(res[0], 0)
  231. def test_server_doesnt_support_SIDH(self):
  232. '''
  233. Client advertises HybridSIDH and ECDH. Server supports ECDH only. Checks weather
  234. TLS session can still be established.
  235. '''
  236. res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true '+self.server_ip+":7443")
  237. self.assertEqual(res[0], 0)
  238. if __name__ == '__main__':
  239. unittest.main()