Alternative TLS implementation in Go
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.

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