% TLS session establishment tests

~ crypto needs_root

# More information at http://www.secdev.org/projects/UTscapy/

############
############
+ TLS server automaton tests

### DISCLAIMER: Those tests are slow ###

= Load server util functions

from __future__ import print_function

import sys, os, re, time, multiprocessing, subprocess

from ast import literal_eval
import os
import sys
from contextlib import contextmanager
from io import BytesIO, StringIO

from scapy.modules import six

from scapy.layers.tls.automaton_srv import TLSServerAutomaton

@contextmanager
def captured_output():
    new_out, new_err = (StringIO(), StringIO()) if six.PY3 else (BytesIO(), BytesIO())
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

def check_output_for_data(out, err, expected_data):
    errored = err.getvalue()
    if errored:
        return (False, errored)
    output = out.getvalue().strip()
    if expected_data:
        for data in output.split('> Received: ')[1:]:
            for line in literal_eval(data).split(b'\n'):
                if line == expected_data:
                    return (True, output)
        return (False, output)
    else:
        return (True, None)

def run_tls_test_server(expected_data, q):
    correct = False
    print("Server started !")
    with captured_output() as (out, err):
        # Prepare automaton
        filename = "/test/tls/pki/srv_cert.pem"
        mycert = os.getenv("SCAPY_ROOT_DIR")+filename if not os.path.exists(filename) else filename
        filename = "/test/tls/pki/srv_key.pem"
        mykey = os.getenv("SCAPY_ROOT_DIR")+filename if not os.path.exists(filename) else filename
        print(os.environ["SCAPY_ROOT_DIR"])
        print(mykey)
        print(mycert)
        assert os.path.exists(mycert)
        assert os.path.exists(mykey)
        t = TLSServerAutomaton(mycert=mycert,
                               mykey=mykey)
        # Sync threads
        q.put(True)
        # Run server automaton
        t.run()
        # Return correct answer
        correct, out_e = check_output_for_data(out, err, expected_data)
    # Return data
    q.put(out_e)

def test_tls_server(suite="", version=""):
    msg = ("TestS_%s_data" % suite).encode()
    # Run server
    q_ = multiprocessing.Manager().Queue()
    th_ = multiprocessing.Process(target=run_tls_test_server, args=(msg, q_))
    th_.start()
    # Synchronise threads
    q_.get()
    time.sleep(1)
    # Run client
    filename = "/test/tls/pki/ca_cert.pem"
    filename = os.getenv("SCAPY_ROOT_DIR")+filename if not os.path.exists(filename) else filename
    CA_f = os.path.abspath(filename)
    p = subprocess.Popen(
        ["openssl", "s_client", "-connect", "127.0.0.1:4433", "-debug", "-cipher", suite, version, "-CAfile", CA_f],
        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
    )
    msg += b"\nstop_server\n"
    out = p.communicate(input=msg)[0]
    print(out.decode())
    if p.returncode != 0:
        th_.terminate()
        raise RuntimeError("OpenSSL returned with error code")
    else:
        p = re.compile(br'verify return:(\d+)')
        _failed = False
        _one_success = False
        for match in p.finditer(out):
            if match.group(1).strip() != b"1":
                _failed = True
                break
            else:
                _one_success = True
        if _failed or not _one_success:
            th_.terminate()
            raise RuntimeError("OpenSSL returned unexpected values")
    # Wait for server
    th_.join(30)
    if th_.is_alive():
        th_.terminate()
        raise RuntimeError("Test timed out")
    # Analyse values
    print(q_.get())


= Testing TLS server with TLS 1.0 and TLS_RSA_WITH_RC4_128_SHA
~ open_ssl_client

test_tls_server("RC4-SHA", "-tls1")

= Testing TLS server with TLS 1.1 and TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
~ open_ssl_client

test_tls_server("EDH-RSA-DES-CBC3-SHA", "-tls1_1")

= Testing TLS server with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
~ open_ssl_client

test_tls_server("DHE-RSA-AES128-SHA256", "-tls1_2")

= Testing TLS server with TLS 1.2 and TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
~ open_ssl_client

test_tls_server("ECDHE-RSA-AES256-GCM-SHA384", "-tls1_2")

+ TLS client automaton tests

= Load client utils functions

import sys, os, time, threading

from scapy.layers.tls.automaton_cli import TLSClientAutomaton
from scapy.layers.tls.handshake import TLSClientHello

from scapy.modules.six.moves.queue import Queue

send_data = cipher_suite_code = version = None

def run_tls_test_client(send_data=None, cipher_suite_code=None, version=None):
    if version == "0002":
        t = TLSClientAutomaton(data=[send_data, b"stop_server", b"quit"], version="sslv2")
    else:
        ch = TLSClientHello(version=int(version, 16), ciphers=int(cipher_suite_code, 16))
        t = TLSClientAutomaton(client_hello=ch, data=[send_data, b"stop_server", b"quit"], debug=1)
    t.run()

def test_tls_client(suite, version, q):
    msg = ("TestC_%s_data" % suite).encode()
    # Run server
    q_ = Queue()
    th_ = threading.Thread(target=run_tls_test_server, args=(msg, q_))
    th_.setDaemon(True)
    th_.start()
    # Synchronise threads
    print("Syncrhonising...")
    assert q_.get() is True
    time.sleep(1)
    print("Thread synchronised")
    # Run client
    run_tls_test_client(msg, suite, version)
    # Wait for server
    print("Client running, waiting...")
    th_.join(60)
    if th_.is_alive():
        raise RuntimeError("Test timed out")
    # Return values
    q.put(q_.get())

def perform_tls_client_test(suite, version):
    # Run test_tls_client in an other thread
    q = Queue()
    p = threading.Thread(target=test_tls_client, args=(suite, version, q))
    p.setDaemon(True)
    p.start()
    # Wait for the function to end
    p.join()
    # Analyse data and return
    if not q.empty():
        print(q.get())
    else:
        print("ERROR: Missing one of the return value detected !")
        assert False

= Testing TLS server and client with SSLv2 and SSL_CK_DES_192_EDE3_CBC_WITH_MD5

perform_tls_client_test("0700c0", "0002")

= Testing TLS client with SSLv3 and TLS_RSA_EXPORT_WITH_RC4_40_MD5

perform_tls_client_test("0003", "0300")

= Testing TLS client with TLS 1.0 and TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA

perform_tls_client_test("0088", "0301")

= Testing TLS client with TLS 1.1 and TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA

perform_tls_client_test("c013", "0302")

= Testing TLS client with TLS 1.2 and TLS_DHE_RSA_WITH_AES_128_GCM_SHA256

perform_tls_client_test("009e", "0303")

= Testing TLS client with TLS 1.2 and TLS_ECDH_anon_WITH_RC4_128_SHA

perform_tls_client_test("c016", "0303")

