Kris Kwiatkowski
8da51abeca
Implements two tests for SIDH/P503-X25519 interoperability. BoringSSL initiates connection to TRIS and TRIS initiates connection to BoringSSL. SIDH server always listens on port 7443
294 lines
11 KiB
Python
Executable File
294 lines
11 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
|
|
import docker
|
|
import unittest
|
|
import re
|
|
import time
|
|
|
|
# Regex patterns used for testing
|
|
|
|
# Checks if TLS 1.3 was negotiated
|
|
RE_PATTERN_HELLO_TLS_13_NORESUME = "^.*Hello TLS 1.3 \(draft .*\) _o/$|^.*Hello TLS 1.3 _o/$"
|
|
# Checks if TLS 1.3 was resumed
|
|
RE_PATTERN_HELLO_TLS_13_RESUME = "Hello TLS 1.3 \[resumed\] _o/"
|
|
# Checks if 0-RTT was used and NOT confirmed
|
|
RE_PATTERN_HELLO_0RTT = "^.*Hello TLS 1.3 .*\[resumed\] \[0-RTT\] _o/$"
|
|
# Checks if 0-RTT was used and confirmed
|
|
RE_PATTERN_HELLO_0RTT_CONFIRMED = "^.*Hello TLS 1.3 .*\[resumed\] \[0-RTT confirmed\] _o/$"
|
|
# ALPN
|
|
RE_PATTERN_ALPN = "ALPN protocol: npn_proto$"
|
|
# Successful TLS establishement from TRIS
|
|
RE_TRIS_ALL_PASSED = ".*All handshakes passed.*"
|
|
# TLS handshake from BoringSSL with SIDH/P503-X25519
|
|
RE_BORINGSSL_P503 = "ECDHE curve: x25519sidh503"
|
|
|
|
class Docker(object):
|
|
''' Utility class used for starting/stoping servers and clients during tests'''
|
|
def __init__(self):
|
|
self.d = docker.from_env()
|
|
|
|
def get_ip(self, server):
|
|
tris_localserver_container = self.d.containers.get(server)
|
|
return tris_localserver_container.attrs['NetworkSettings']['IPAddress']
|
|
|
|
def run_client(self, image_name, cmd):
|
|
''' Runs client and returns tuple (status_code, logs) '''
|
|
c = self.d.containers.create(image=image_name, command=cmd)
|
|
c.start()
|
|
res = c.wait()
|
|
ret = c.logs()
|
|
c.remove()
|
|
return (res['StatusCode'], ret)
|
|
|
|
def run_server(self, image_name, cmd=None, ports=None, entrypoint=None):
|
|
''' Starts server and returns docker container '''
|
|
c = self.d.containers.create(image=image_name, detach=True, command=cmd, ports=ports, entrypoint=entrypoint)
|
|
c.start()
|
|
# TODO: maybe can be done better?
|
|
time.sleep(3)
|
|
return c
|
|
|
|
class RegexSelfTest(unittest.TestCase):
|
|
''' Ensures that those regexe's actually work '''
|
|
|
|
LINE_HELLO_TLS ="\nsomestuff\nHello TLS 1.3 _o/\nsomestuff"
|
|
LINE_HELLO_DRAFT_TLS="\nsomestuff\nHello TLS 1.3 (draft 23) _o/\nsomestuff"
|
|
|
|
LINE_HELLO_RESUMED ="\nsomestuff\nHello TLS 1.3 [resumed] _o/\nsomestuff"
|
|
LINE_HELLO_MIXED ="\nsomestuff\nHello TLS 1.3 (draft 23) _o/\nHello TLS 1.3 (draft 23) [resumed] _o/\nsomestuff"
|
|
LINE_HELLO_TLS_12 ="\nsomestuff\nHello TLS 1.2 (draft 23) [resumed] _o/\nsomestuff"
|
|
LINE_HELLO_TLS_13_0RTT="\nsomestuff\nHello TLS 1.3 (draft 23) [resumed] [0-RTT] _o/\nsomestuff"
|
|
LINE_HELLO_TLS_13_0RTT_CONFIRMED="\nsomestuff\nHello TLS 1.3 (draft 23) [resumed] [0-RTT confirmed] _o/\nsomestuff"
|
|
def test_regexes(self):
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_TLS, re.MULTILINE))
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_DRAFT_TLS, re.MULTILINE))
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_RESUMED, re.MULTILINE))
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT_CONFIRMED, RegexSelfTest.LINE_HELLO_TLS_13_0RTT_CONFIRMED, re.MULTILINE))
|
|
|
|
# negative cases
|
|
|
|
# expects 1.3, but 1.2 received
|
|
self.assertIsNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, RegexSelfTest.LINE_HELLO_TLS_12, re.MULTILINE))
|
|
# expects 0-RTT
|
|
self.assertIsNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
|
|
# expectes 0-RTT confirmed
|
|
self.assertIsNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT, RegexSelfTest.LINE_HELLO_TLS_13_0RTT_CONFIRMED, re.MULTILINE))
|
|
# expects resume without 0-RTT
|
|
self.assertIsNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, RegexSelfTest.LINE_HELLO_TLS_13_0RTT, re.MULTILINE))
|
|
|
|
|
|
class InteropServer(object):
|
|
''' Instantiates TRIS as a server '''
|
|
|
|
TRIS_SERVER_NAME = "tris-localserver"
|
|
|
|
@classmethod
|
|
def setUpClass(self):
|
|
self.d = Docker()
|
|
self.server = self.d.run_server(self.TRIS_SERVER_NAME)
|
|
|
|
@classmethod
|
|
def tearDownClass(self):
|
|
self.server.kill()
|
|
self.server.remove()
|
|
|
|
@property
|
|
def server_ip(self):
|
|
return self.d.get_ip(self.server.name)
|
|
|
|
# Mixins for testing server functionality
|
|
|
|
class ServerNominalMixin(object):
|
|
''' Nominal tests for TLS 1.3 - client tries to perform handshake with server '''
|
|
def test_rsa(self):
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":"+'1443')
|
|
self.assertTrue(res[0] == 0)
|
|
# Check there was TLS hello without resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
|
|
# Check there was TLS hello with resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
|
|
|
|
def test_ecdsa(self):
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":"+'2443')
|
|
self.assertTrue(res[0] == 0)
|
|
# Check there was TLS hello without resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
|
|
# Check there was TLS hello with resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
|
|
|
|
class ServerClientAuthMixin(object):
|
|
''' Client authentication testing '''
|
|
def test_client_auth(self):
|
|
args = ''.join([self.server_ip+':6443',' -key client_rsa.key -cert client_rsa.crt -debug'])
|
|
res = self.d.run_client(self.CLIENT_NAME, args)
|
|
self.assertEqual(res[0], 0)
|
|
# Check there was TLS hello without resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
|
|
# Check there was TLS hello with resume
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
|
|
|
|
class ClientNominalMixin(object):
|
|
|
|
def test_rsa(self):
|
|
res = self.d.run_client(self.CLIENT_NAME, '-ecdsa=false '+self.server_ip+":1443")
|
|
self.assertEqual(res[0], 0)
|
|
|
|
def test_ecdsa(self):
|
|
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false '+self.server_ip+":2443")
|
|
self.assertEqual(res[0], 0)
|
|
|
|
|
|
class ClientClientAuthMixin(object):
|
|
''' Client authentication testing - tris on client side '''
|
|
|
|
def test_client_auth(self):
|
|
res = self.d.run_client('tris-testclient', '-rsa=false -cliauth '+self.server_ip+":6443")
|
|
self.assertTrue(res[0] == 0)
|
|
|
|
class ServerZeroRttMixin(object):
|
|
''' Zero RTT testing '''
|
|
|
|
def test_zero_rtt(self):
|
|
# rejecting 0-RTT
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":3443")
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_TLS_13_RESUME, res[1], re.MULTILINE))
|
|
self.assertIsNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT, res[1], re.MULTILINE))
|
|
|
|
# accepting 0-RTT
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":4443")
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT, res[1], re.MULTILINE))
|
|
|
|
# confirming 0-RTT
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":5443")
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(
|
|
re.search(RE_PATTERN_HELLO_0RTT_CONFIRMED, res[1], re.MULTILINE))
|
|
|
|
class InteropClient(object):
|
|
''' Instantiates TRIS as a client '''
|
|
|
|
CLIENT_NAME = "tris-testclient"
|
|
|
|
@classmethod
|
|
def setUpClass(self):
|
|
self.d = Docker()
|
|
self.server = self.d.run_server(
|
|
self.SERVER_NAME,
|
|
ports={ '1443/tcp': 1443, '2443/tcp': 2443, '6443/tcp': 6443, '7443/tcp': 7443},
|
|
entrypoint="/server.sh")
|
|
|
|
@classmethod
|
|
def tearDownClass(self):
|
|
self.server.kill()
|
|
self.server.remove()
|
|
|
|
@property
|
|
def server_ip(self):
|
|
return self.d.get_ip(self.server.name)
|
|
|
|
# Actual test definition
|
|
|
|
# TRIS as a server, BoringSSL as a client
|
|
class InteropServer_BoringSSL(InteropServer, ServerNominalMixin, ServerClientAuthMixin, unittest.TestCase):
|
|
|
|
CLIENT_NAME = "tls-tris:boring"
|
|
|
|
def test_ALPN(self):
|
|
'''
|
|
Checks wether ALPN is sent back by tris server in EncryptedExtensions in case of TLS 1.3. The
|
|
ALPN protocol is set to 'npn_proto', which is hardcoded in TRIS test server.
|
|
'''
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":1443 "+'-alpn-protos npn_proto')
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(re.search(RE_PATTERN_ALPN, res[1], re.MULTILINE))
|
|
|
|
def test_SIDH(self):
|
|
'''
|
|
Connects to TRIS server listening on 7443 and tries to perform key agreement with SIDH/P503-X25519
|
|
'''
|
|
res = self.d.run_client(self.CLIENT_NAME, self.server_ip+":7443 "+'-curves x25519sidh503')
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(re.search(RE_BORINGSSL_P503, res[1], re.MULTILINE))
|
|
self.assertIsNotNone(re.search(RE_PATTERN_HELLO_TLS_13_NORESUME, res[1], re.MULTILINE))
|
|
|
|
# PicoTLS doesn't seem to implement draft-23 correctly. It will
|
|
# be enabled when draft-28 is implemented.
|
|
# class InteropServer_PicoTLS(
|
|
# InteropServer,
|
|
# ServerNominalMixin,
|
|
# ServerZeroRttMixin,
|
|
# unittest.TestCase
|
|
# ): CLIENT_NAME = "tls-tris:picotls"
|
|
|
|
class InteropServer_NSS(
|
|
InteropServer,
|
|
ServerNominalMixin,
|
|
ServerZeroRttMixin,
|
|
unittest.TestCase
|
|
): CLIENT_NAME = "tls-tris:tstclnt"
|
|
|
|
# TRIS as a client, BoringSSL as a server
|
|
class InteropClient_BoringSSL(InteropClient, ClientNominalMixin, ClientClientAuthMixin, unittest.TestCase):
|
|
|
|
SERVER_NAME = "boring-localserver"
|
|
|
|
def test_SIDH(self):
|
|
'''
|
|
Connects to BoringSSL server listening on 7443 and tries to perform key agreement with SIDH/P503-X25519
|
|
'''
|
|
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -qr SIDH-P503-X25519 ' + self.server_ip+":7443")
|
|
self.assertEqual(res[0], 0)
|
|
self.assertIsNotNone(re.search(RE_TRIS_ALL_PASSED, res[1], re.MULTILINE))
|
|
|
|
class InteropClient_NSS(
|
|
InteropClient,
|
|
ClientNominalMixin,
|
|
unittest.TestCase
|
|
): SERVER_NAME = "tstclnt-localserver"
|
|
|
|
# TRIS as a client
|
|
class InteropServer_TRIS(ClientNominalMixin, InteropServer, unittest.TestCase):
|
|
|
|
CLIENT_NAME = 'tris-testclient'
|
|
|
|
def test_client_auth(self):
|
|
# I need to block TLS v1.2 as test server needs some rework
|
|
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=false -cliauth '+self.server_ip+":6443")
|
|
self.assertEqual(res[0], 0)
|
|
|
|
def test_SIDH(self):
|
|
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true -qr SIDH-P503-X25519 '+self.server_ip+":7443")
|
|
self.assertEqual(res[0], 0)
|
|
|
|
def test_server_doesnt_support_SIDH(self):
|
|
'''
|
|
Client advertises HybridSIDH and ECDH. Server supports ECDH only. Checks weather
|
|
TLS session can still be established.
|
|
'''
|
|
res = self.d.run_client(self.CLIENT_NAME, '-rsa=false -ecdsa=true '+self.server_ip+":7443")
|
|
self.assertEqual(res[0], 0)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|