Esempio n. 1
0
    def test_min_eq_max(self):
        it = structured_random_iter(1, min_length=16, max_length=16, step=16)

        ret = [i for i in it]

        self.assertEqual(len(ret), 1)
        self.assertIsInstance(ret[0], StructuredRandom)
        self.assertEqual(len(ret[0].data), 16)
Esempio n. 2
0
    def test_min_and_max(self):
        for _ in range(100):
            it = structured_random_iter(1,
                                        min_length=16,
                                        max_length=256,
                                        step=16)

            ret = [i for i in it]

            self.assertIn(len(ret), [1])
            for r in ret:
                self.assertIsInstance(r, StructuredRandom)
                self.assertIn(len(r.data), range(16, 257, 16))
                if r.vals[0][1] is not None:
                    self.assertEqual(r.data[0], r.vals[0][1])
Esempio n. 3
0
def main():
    """check if incorrect padding is rejected by server"""
    host = "localhost"
    port = 4433
    num_limit = 800
    run_exclude = set()

    argv = sys.argv[1:]
    opts, args = getopt.getopt(argv, "h:p:e:n:", ["help"])
    for opt, arg in opts:
        if opt == '-h':
            host = arg
        elif opt == '-p':
            port = int(arg)
        elif opt == '-e':
            run_exclude.add(arg)
        elif opt == '-n':
            num_limit = int(arg)
        elif opt == '--help':
            help_msg()
            sys.exit(0)
        else:
            raise ValueError("Unknown option: {0}".format(opt))

    if args:
        run_only = set(args)
    else:
        run_only = None

    conversations = {}

    conversation = Connect(host, port)
    node = conversation
    ciphers = [
        CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
        CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    ]
    node = node.add_child(ClientHelloGenerator(ciphers))
    node = node.add_child(ExpectServerHello())
    node = node.add_child(ExpectCertificate())
    node = node.add_child(ExpectCertificateRequest())
    fork = node
    node = node.add_child(ExpectServerHelloDone())
    node = node.add_child(CertificateGenerator())

    # handle servers which ask for client certificates
    fork.next_sibling = ExpectServerHelloDone()
    join = ClientKeyExchangeGenerator()
    fork.next_sibling.add_child(join)

    node = node.add_child(join)
    node = node.add_child(ChangeCipherSpecGenerator())
    node = node.add_child(FinishedGenerator())
    node = node.add_child(ExpectChangeCipherSpec())
    node = node.add_child(ExpectFinished())
    node = node.add_child(ApplicationDataGenerator(b"GET / HTTP/1.0\r\n\r\n"))
    node = node.add_child(ExpectApplicationData())
    node = node.add_child(
        AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))
    node = node.add_child(
        ExpectAlert(AlertLevel.warning, AlertDescription.close_notify))
    node.next_sibling = ExpectClose()
    node = node.add_child(ExpectClose())
    conversations["sanity"] = \
            conversation

    # block size is 16 bytes for AES_128, 2**14 is the TLS protocol max
    for data in structured_random_iter(num_limit,
                                       min_length=16,
                                       max_length=2**14,
                                       step=16):
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        node = node.add_child(ClientHelloGenerator(ciphers))
        node = node.add_child(ExpectServerHello())
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateRequest())
        fork = node
        node = node.add_child(ExpectServerHelloDone())
        node = node.add_child(CertificateGenerator())

        # handle servers which ask for client certificates
        fork.next_sibling = ExpectServerHelloDone()
        join = ClientKeyExchangeGenerator()
        fork.next_sibling.add_child(join)

        node = node.add_child(join)
        node = node.add_child(ChangeCipherSpecGenerator())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(ExpectFinished())
        node = node.add_child(
            replace_plaintext(
                ApplicationDataGenerator(
                    b"I'm ignored, only type is important"), data.data))
        node = node.add_child(
            ExpectAlert(AlertLevel.fatal, AlertDescription.bad_record_mac))
        node = node.add_child(ExpectClose())
        conversations["encrypted plaintext of {0}".format(data)] = \
                conversation

    # run the conversation
    good = 0
    bad = 0
    failed = []
    if not num_limit:
        num_limit = len(conversations)

    # make sure that sanity test is run first and last
    # to verify that server was running and kept running throught
    sanity_test = ('sanity', conversations['sanity'])
    ordered_tests = chain([sanity_test],
                          islice(
                              filter(lambda x: x[0] != 'sanity',
                                     conversations.items()), num_limit),
                          [sanity_test])

    for c_name, c_test in ordered_tests:
        if run_only and c_name not in run_only or c_name in run_exclude:
            continue
        print("{0} ...".format(c_name))

        runner = Runner(c_test)

        res = True
        try:
            runner.run()
        except Exception:
            print("Error while processing")
            print(traceback.format_exc())
            res = False

        if res:
            good += 1
            print("OK\n")
        else:
            bad += 1
            failed.append(c_name)

    print("Test end")
    print("successful: {0}".format(good))
    print("failed: {0}".format(bad))
    failed_sorted = sorted(failed, key=natural_sort_keys)
    print("  {0}".format('\n  '.join(repr(i) for i in failed_sorted)))

    if bad > 0:
        sys.exit(1)
Esempio n. 4
0
def main():
    """Check if incorrect padding and MAC is rejected by server."""
    host = "localhost"
    port = 4433
    num_limit = 1024
    rand_limit = None
    run_exclude = set()
    expected_failures = {}
    last_exp_tmp = None
    dhe = False
    cipher = None
    splitting = None

    argv = sys.argv[1:]
    opts, args = getopt.getopt(argv, "h:p:e:x:X:n:dC:", ["help", "random=",
                                                     "1/n-1", "0/n"])
    for opt, arg in opts:
        if opt == '-h':
            host = arg
        elif opt == '-p':
            port = int(arg)
        elif opt == '-e':
            run_exclude.add(arg)
        elif opt == '-x':
            expected_failures[arg] = None
            last_exp_tmp = str(arg)
        elif opt == '-X':
            if not last_exp_tmp:
                raise ValueError("-x has to be specified before -X")
            expected_failures[last_exp_tmp] = str(arg)
        elif opt == '-n':
            num_limit = int(arg)
        elif opt == '--random':
            rand_limit = int(arg)//2
        elif opt == '-d':
            dhe = True
        elif opt == '--1/n-1':
            splitting = 1
        elif opt == '--0/n':
            splitting = 0
        elif opt == '-C':
            if arg[:2] == '0x':
                cipher = int(arg, 16)
            else:
                try:
                    cipher = getattr(CipherSuite, arg)
                except AttributeError:
                    cipher = int(arg)
        elif opt == '--help':
            help_msg()
            sys.exit(0)
        else:
            raise ValueError("Unknown option: {0}".format(opt))

    if dhe and cipher is not None:
        raise ValueError("-C and -d are mutually exclusive")
    if cipher is None:
        if dhe:
            cipher = CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
        else:
            cipher = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA

    block_size = 16
    if cipher in CipherSuite.tripleDESSuites:
        block_size = 8

    if rand_limit is None:
        if block_size == 16:
            rand_limit = 4096
        else:
            assert block_size == 8
            rand_limit = 8192

    dhe = cipher in CipherSuite.ecdhAllSuites or \
            cipher in CipherSuite.dhAllSuites

    if args:
        run_only = set(args)
    else:
        run_only = None
    # if we are to execute only some tests, we need to not filter the
    # static ones
    if run_only:
        num_limit = None

    conversations = {}

    conversation = Connect(host, port)
    node = conversation
    if dhe:
        ext = {}
        add_dhe_extensions(ext)
    else:
        ext = None
    ciphers = [cipher,
               CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
    node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
    node = node.add_child(ExpectServerHello())
    node = node.add_child(ExpectCertificate())
    if dhe:
        node = node.add_child(ExpectServerKeyExchange())

    # handle servers that ask for client certificates
    node = node.add_child(ExpectCertificateRequest())
    fork = node
    node = node.add_child(ExpectServerHelloDone())
    node = node.add_child(CertificateGenerator())

    # handle servers that don't ask for client certificates
    fork.next_sibling = ExpectServerHelloDone()

    # join both paths
    join = ClientKeyExchangeGenerator()
    fork.next_sibling.add_child(join)
    node = node.add_child(join)

    node = node.add_child(ChangeCipherSpecGenerator())
    node = node.add_child(FinishedGenerator())
    node = node.add_child(ExpectChangeCipherSpec())
    node = node.add_child(ExpectFinished())
    node = node.add_child(
        ApplicationDataGenerator(b"GET / HTTP/1.0\r\n\r\n"))
    if splitting is not None:
        node = node.add_child(ExpectApplicationData(size=splitting))
    node = node.add_child(ExpectApplicationData())
    node = node.add_child(AlertGenerator(AlertLevel.warning,
                                         AlertDescription.close_notify))
    node = node.add_child(ExpectAlert(AlertLevel.warning,
                                      AlertDescription.close_notify))
    node.next_sibling = ExpectClose()
    node = node.add_child(ExpectClose())
    conversations["sanity"] = \
            conversation

    # test all combinations of lengths and values for plaintexts up to 256
    # bytes long with uniform content (where every byte has the same value)
    mono_tests = [(length, value) for length in
                  range(block_size, 257, block_size)
                  for value in range(256)]
    if not num_limit:
        mono_iter = mono_tests
        rand_len_to_generate = rand_limit
    else:
        # we want to speed up generation, so generate only as many conversations
        # as necessary to meet num_limit, but do that uniformely between
        # random payloads, mono payloads, application_data tests and handshake
        # tests
        ratio = rand_limit * 1.0 / len(mono_tests)

        # `num_limit / 2` because of handshake and application_data tests
        mono_len_to_generate = min(len(mono_tests),
                                   int(ceil((num_limit / 2) * (1 - ratio))))
        rand_len_to_generate = int(ceil((num_limit) / 2 * ratio))
        mono_iter = sample(mono_tests, mono_len_to_generate)

    mono = (StructuredRandom([(length, value)]) for length, value in
            mono_iter)

    # 2**14 is the TLS protocol max
    rand = structured_random_iter(rand_len_to_generate,
                                  min_length=block_size, max_length=2**14,
                                  step=block_size)

    if not run_only:
        for data in chain(mono, rand):
            add_app_data_conversation(conversations, host, port, cipher, dhe, data)
    else:
        for conv in run_only:
            if "Application Data" in conv:
                params = parse_structured_random_params(conv)
                data = StructuredRandom(params)
                add_app_data_conversation(conversations, host, port, cipher,
                                          dhe, data)

    # do th same thing but for handshake record
    # (note, while the type is included in the MAC, we are never
    # sending a valid MAC, so the server has only the record layer header to
    # deduce if the message needs special handling, if any)

    # test all combinations of lengths and values for plaintexts up to 256
    # bytes long with uniform content (where every byte has the same value)
    mono_tests = [(length, value) for length in
                  range(block_size, 257, block_size)
                  for value in range(256)]
    if not num_limit:
        mono_iter = mono_tests
        rand_len_to_generate = rand_limit
    else:
        # we want to speed up generation, so generate only as many conversations
        # as necessary to meet num_limit, but do that uniformely between
        # random payloads, mono payloads, application_data tests and handshake
        # tests
        ratio = rand_limit * 1.0 / len(mono_tests)

        # `num_limit / 2` because of handshake and application_data tests
        mono_len_to_generate = min(len(mono_tests),
                                   int(ceil((num_limit / 2) * (1 - ratio))))
        rand_len_to_generate = int(ceil((num_limit) / 2 * ratio))
        mono_iter = sample(mono_tests, mono_len_to_generate)

    mono = (StructuredRandom([(length, value)]) for length, value in
            mono_iter)

    # 2**14 is the TLS protocol max
    rand = structured_random_iter(rand_len_to_generate,
                                  min_length=block_size, max_length=2**14,
                                  step=block_size)

    if not run_only:
        for data in chain(mono, rand):
            add_handshake_conversation(conversations, host, port, cipher, dhe,
                                       data)
    else:
        for conv in run_only:
            if "Handshake" in conv:
                params = parse_structured_random_params(conv)
                data = StructuredRandom(params)
                add_handshake_conversation(conversations, host,
                                           port, cipher,
                                           dhe, data)

    # run the conversation
    good = 0
    bad = 0
    xfail = 0
    xpass = 0
    failed = []
    xpassed = []
    if not num_limit:
        num_limit = len(conversations)

    # make sure that sanity test is run first and last
    # to verify that server was running and kept running throughout
    sanity_tests = [('sanity', conversations['sanity'])]
    regular_tests = [(k, v) for k, v in conversations.items() if k != 'sanity']
    sampled_tests = sample(regular_tests, min(num_limit, len(regular_tests)))
    ordered_tests = chain(sanity_tests, sampled_tests, sanity_tests)

    for c_name, c_test in ordered_tests:
        if run_only and c_name not in run_only or c_name in run_exclude:
            continue
        print("{0} ...".format(c_name))

        runner = Runner(c_test)

        res = True
        exception = None
        try:
            runner.run()
        except Exception as exp:
            exception = exp
            print("Error while processing")
            print(traceback.format_exc())
            res = False

        if c_name in expected_failures:
            if res:
                xpass += 1
                xpassed.append(c_name)
                print("XPASS: expected failure but test passed\n")
            else:
                if expected_failures[c_name] is not None and  \
                    expected_failures[c_name] not in str(exception):
                        bad += 1
                        failed.append(c_name)
                        print("Expected error message: {0}\n"
                            .format(expected_failures[c_name]))
                else:
                    xfail += 1
                    print("OK-expected failure\n")
        else:
            if res:
                good += 1
                print("OK\n")
            else:
                bad += 1
                failed.append(c_name)

    print("Tester for de-padding and MAC verification\n")
    print("Generates plaintexts that can be incorrectly handled by de-padding")
    print("and MAC verification algorithms and verifies that they are handled")
    print("correctly and consistently.\n")
    print("Should be executed with multiple ciphers (especially regarding the")
    print("HMAC used) and TLS versions. Note: test requires CBC mode")
    print("ciphers.\n")
    print("TLS 1.0 servers should require enabling BEAST workaround, see")
    print("help message.\n")
    print("version: {0}\n".format(version))

    print("Test end")
    print(20 * '=')
    print("TOTAL: {0}".format(len(sampled_tests) + 2*len(sanity_tests)))
    print("SKIP: {0}".format(len(run_exclude.intersection(conversations.keys()))))
    print("PASS: {0}".format(good))
    print("XFAIL: {0}".format(xfail))
    print("FAIL: {0}".format(bad))
    print("XPASS: {0}".format(xpass))
    print(20 * '=')
    sort = sorted(xpassed ,key=natural_sort_keys)
    if len(sort):
        print("XPASSED:\n\t{0}".format('\n\t'.join(repr(i) for i in sort)))
    sort = sorted(failed, key=natural_sort_keys)
    if len(sort):
        print("FAILED:\n\t{0}".format('\n\t'.join(repr(i) for i in sort)))

    if bad > 0:
        sys.exit(1)
Esempio n. 5
0
def main():
    host = "localhost"
    port = 4433
    num_limit = 20
    run_exclude = set()
    expected_failures = {}
    last_exp_tmp = None
    ext_exclude = set()
    exp_sup_groups = False
    individual_ext = []

    argv = sys.argv[1:]
    opts, args = getopt.getopt(argv, "h:p:e:x:X:n:",
                               ["help", "exc=", "supgroup", "separate="])
    for opt, arg in opts:
        if opt == '-h':
            host = arg
        elif opt == '-p':
            port = int(arg)
        elif opt == '-e':
            run_exclude.add(arg)
        elif opt == '-x':
            expected_failures[arg] = None
            last_exp_tmp = str(arg)
        elif opt == '-X':
            if not last_exp_tmp:
                raise ValueError("-x has to be specified before -X")
            expected_failures[last_exp_tmp] = str(arg)
        elif opt == '-n':
            num_limit = int(arg)
        elif opt == '--exc':
            ext_exclude.add(int(arg))
        elif opt == '--supgroup':
            exp_sup_groups = True
        elif opt == "--separate":
            individual_ext.append(int(arg))
        elif opt == '--help':
            help_msg()
            sys.exit(0)
        else:
            raise ValueError("Unknown option: {0}".format(opt))

    if args:
        run_only = set(args)
    else:
        run_only = None

    conversations = {}

    conversation = Connect(host, port)
    node = conversation
    ciphers = [
        CipherSuite.TLS_AES_128_GCM_SHA256,
        CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    ]
    ext = {}
    groups = [GroupName.secp256r1]
    key_shares = []
    for group in groups:
        key_shares.append(key_share_gen(group))
    ext[ExtensionType.key_share] = ClientKeyShareExtension().create(key_shares)
    ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
        .create([TLS_1_3_DRAFT])
    ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
        .create(groups)
    sig_algs = [
        SignatureScheme.rsa_pss_rsae_sha256, SignatureScheme.rsa_pss_pss_sha256
    ]
    ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
        .create(sig_algs)
    ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
        .create(RSA_SIG_ALL)
    node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
    node = node.add_child(ExpectServerHello())
    node = node.add_child(ExpectChangeCipherSpec())
    node = node.add_child(ExpectEncryptedExtensions())
    node = node.add_child(ExpectCertificate())
    node = node.add_child(ExpectCertificateVerify())
    node = node.add_child(ExpectFinished())
    node = node.add_child(FinishedGenerator())
    node = node.add_child(
        ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

    # This message is optional and may show up 0 to many times
    cycle = ExpectNewSessionTicket()
    node = node.add_child(cycle)
    node.add_child(cycle)

    node.next_sibling = ExpectApplicationData()
    node = node.next_sibling.add_child(
        AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

    node = node.add_child(ExpectAlert())
    node.next_sibling = ExpectClose()
    conversations["sanity"] = conversation

    expect_exts_sh = {
        ExtensionType.key_share: None,
        ExtensionType.supported_versions: None
    }
    expect_exts_ee = {}
    if exp_sup_groups:
        expect_exts_ee[ExtensionType.supported_groups] = None

    unassigned_ext_id = []
    unassigned_ext_id.extend(range(2, 5))
    unassigned_ext_id.extend(range(6, 10))
    unassigned_ext_id.extend([11, 12, 17])
    unassigned_ext_id.extend(range(22, 28))
    unassigned_ext_id.extend(range(29, 41))
    unassigned_ext_id.extend([46])
    unassigned_ext_id.extend(range(52, 65536))

    # Exclude extensions from a list of unassigned ones
    unassigned_ext_id = [
        ext for ext in unassigned_ext_id if ext not in ext_exclude
    ]

    chunk_size = 4096
    for ext_chunk in (unassigned_ext_id[j:j + chunk_size]
                      for j in range(0, len(unassigned_ext_id), chunk_size)):
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        ext = OrderedDict()
        for ext_id in ext_chunk:
            ext[ext_id] = AutoEmptyExtension()
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations[
            "empty unassigned extensions, ids in range from {0} to {1}".format(
                ext_chunk[0], ext_chunk[-1])] = conversation

    chunk_size = 1024
    for ext_chunk in (unassigned_ext_id[j:j + chunk_size]
                      for j in range(0, len(unassigned_ext_id), chunk_size)):
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        random_payload = structured_random_iter(max_length=2**6,
                                                count=len(ext_chunk))
        ext = OrderedDict()
        for ext_id in ext_chunk:
            ext[ext_id] = TLSExtension(extType=ext_id).create(
                next(random_payload).data)
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations[
            "unassigned extensions with random payload, ids in range from {0} to {1}"
            .format(ext_chunk[0], ext_chunk[-1])] = conversation

    # test the special one user asked for
    for ext_id in individual_ext:
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        ext = OrderedDict()
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        ext[ext_id] = AutoEmptyExtension()
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations["empty unassigned extension id {0}".format(
            ext_id)] = conversation

        # and now with random payload
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        ext = OrderedDict()
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        random_payload = structured_random_iter(max_length=2**6, count=1)
        ext[ext_id] = TLSExtension(extType=ext_id)\
            .create(next(random_payload).data)
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations["unassigned extension with random payload, id {0}".
                      format(ext_id)] = conversation

    # run the conversation
    good = 0
    bad = 0
    xfail = 0
    xpass = 0
    failed = []
    xpassed = []
    if not num_limit:
        num_limit = len(conversations)

    # make sure that sanity test is run first and last
    # to verify that server was running and kept running throughout
    sanity_tests = [('sanity', conversations['sanity'])]
    if run_only:
        if num_limit > len(run_only):
            num_limit = len(run_only)
        regular_tests = [(k, v) for k, v in conversations.items()
                         if k in run_only]
    else:
        regular_tests = [(k, v) for k, v in conversations.items()
                         if (k != 'sanity') and k not in run_exclude]
    sampled_tests = sample(regular_tests, min(num_limit, len(regular_tests)))
    ordered_tests = chain(sanity_tests, sampled_tests, sanity_tests)

    for c_name, c_test in ordered_tests:
        print("{0} ...".format(c_name))

        runner = Runner(c_test)

        res = True
        exception = None
        try:
            runner.run()
        except Exception as exp:
            exception = exp
            print("Error while processing")
            print(traceback.format_exc())
            res = False

        if c_name in expected_failures:
            if res:
                xpass += 1
                xpassed.append(c_name)
                print("XPASS-expected failure but test passed\n")
            else:
                if expected_failures[c_name] is not None and  \
                    expected_failures[c_name] not in str(exception):
                    bad += 1
                    failed.append(c_name)
                    print("Expected error message: {0}\n".format(
                        expected_failures[c_name]))
                else:
                    xfail += 1
                    print("OK-expected failure\n")
        else:
            if res:
                good += 1
                print("OK\n")
            else:
                bad += 1
                failed.append(c_name)

    print("Test with large number of unassigned extensions in TLS 1.3")
    print("Verify that server does not reply to any of these extensions")
    print("and establish regular session.\n")

    print("Test end")
    print(20 * '=')
    print("version: {0}".format(version))
    print(20 * '=')
    print("TOTAL: {0}".format(len(sampled_tests) + 2 * len(sanity_tests)))
    print("SKIP: {0}".format(
        len(run_exclude.intersection(conversations.keys()))))
    print("PASS: {0}".format(good))
    print("XFAIL: {0}".format(xfail))
    print("FAIL: {0}".format(bad))
    print("XPASS: {0}".format(xpass))
    print(20 * '=')
    sort = sorted(xpassed, key=natural_sort_keys)
    if len(sort):
        print("XPASSED:\n\t{0}".format('\n\t'.join(repr(i) for i in sort)))
    sort = sorted(failed, key=natural_sort_keys)
    if len(sort):
        print("FAILED:\n\t{0}".format('\n\t'.join(repr(i) for i in sort)))

    if bad > 0:
        sys.exit(1)
def main():
    host = "localhost"
    port = 4433
    num_limit = None
    run_exclude = set()
    ext_exclude = set()
    exp_sup_groups = False

    argv = sys.argv[1:]
    opts, args = getopt.getopt(argv, "h:p:e:n:", ["help", "exc=", "supgroup"])
    for opt, arg in opts:
        if opt == '-h':
            host = arg
        elif opt == '-p':
            port = int(arg)
        elif opt == '-e':
            run_exclude.add(arg)
        elif opt == '-n':
            num_limit = int(arg)
        elif opt == '--exc':
            ext_exclude.add(int(arg))
        elif opt == '--supgroup':
            exp_sup_groups = True
        elif opt == '--help':
            help_msg()
            sys.exit(0)
        else:
            raise ValueError("Unknown option: {0}".format(opt))

    if args:
        run_only = set(args)
    else:
        run_only = None

    conversations = {}

    conversation = Connect(host, port)
    node = conversation
    ciphers = [
        CipherSuite.TLS_AES_128_GCM_SHA256,
        CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    ]
    ext = {}
    groups = [GroupName.secp256r1]
    key_shares = []
    for group in groups:
        key_shares.append(key_share_gen(group))
    ext[ExtensionType.key_share] = ClientKeyShareExtension().create(key_shares)
    ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
        .create([TLS_1_3_DRAFT])
    ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
        .create(groups)
    sig_algs = [
        SignatureScheme.rsa_pss_rsae_sha256, SignatureScheme.rsa_pss_pss_sha256
    ]
    ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
        .create(sig_algs)
    ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
        .create(RSA_SIG_ALL)
    node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
    node = node.add_child(ExpectServerHello())
    node = node.add_child(ExpectChangeCipherSpec())
    node = node.add_child(ExpectEncryptedExtensions())
    node = node.add_child(ExpectCertificate())
    node = node.add_child(ExpectCertificateVerify())
    node = node.add_child(ExpectFinished())
    node = node.add_child(FinishedGenerator())
    node = node.add_child(
        ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

    # This message is optional and may show up 0 to many times
    cycle = ExpectNewSessionTicket()
    node = node.add_child(cycle)
    node.add_child(cycle)

    node.next_sibling = ExpectApplicationData()
    node = node.next_sibling.add_child(
        AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

    node = node.add_child(ExpectAlert())
    node.next_sibling = ExpectClose()
    conversations["sanity"] = conversation

    expect_exts_sh = {
        ExtensionType.key_share: None,
        ExtensionType.supported_versions: None
    }
    expect_exts_ee = {}
    if exp_sup_groups:
        expect_exts_ee[ExtensionType.supported_groups] = None

    unassigned_ext_id = []
    unassigned_ext_id.extend(range(2, 5))
    unassigned_ext_id.extend(range(6, 10))
    unassigned_ext_id.extend([11, 12, 17])
    unassigned_ext_id.extend(range(22, 28))
    unassigned_ext_id.extend(range(29, 41))
    unassigned_ext_id.extend([46])
    unassigned_ext_id.extend(range(52, 65536))

    # Exclude extensions from a list of unassigned ones
    unassigned_ext_id = [
        ext for ext in unassigned_ext_id if ext not in ext_exclude
    ]

    chunk_size = 4096
    for ext_chunk in (unassigned_ext_id[j:j + chunk_size]
                      for j in range(0, len(unassigned_ext_id), chunk_size)):
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        ext = OrderedDict()
        for ext_id in ext_chunk:
            ext[ext_id] = AutoEmptyExtension()
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations[
            "empty unassigned extensions, ids in range from {0} to {1}".format(
                ext_chunk[0], ext_chunk[-1])] = conversation

    chunk_size = 1024
    for ext_chunk in (unassigned_ext_id[j:j + chunk_size]
                      for j in range(0, len(unassigned_ext_id), chunk_size)):
        conversation = Connect(host, port)
        node = conversation
        ciphers = [
            CipherSuite.TLS_AES_128_GCM_SHA256,
            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
        ]
        random_payload = structured_random_iter(max_length=2**6,
                                                count=len(ext_chunk))
        ext = OrderedDict()
        for ext_id in ext_chunk:
            ext[ext_id] = TLSExtension(extType=ext_id).create(
                next(random_payload).data)
        if ExtensionType.renegotiation_info in ext:
            ext[ExtensionType.renegotiation_info] = None

        groups = [GroupName.secp256r1]
        key_shares = []
        for group in groups:
            key_shares.append(key_share_gen(group))
        ext[ExtensionType.key_share] = ClientKeyShareExtension().create(
            key_shares)
        ext[ExtensionType.supported_versions] = SupportedVersionsExtension()\
            .create([TLS_1_3_DRAFT])
        ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\
            .create(groups)
        sig_algs = [
            SignatureScheme.rsa_pss_rsae_sha256,
            SignatureScheme.rsa_pss_pss_sha256
        ]
        ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\
            .create(sig_algs)
        ext[ExtensionType.signature_algorithms_cert] = SignatureAlgorithmsCertExtension()\
            .create(RSA_SIG_ALL)
        node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext))
        node = node.add_child(ExpectServerHello(extensions=expect_exts_sh))
        node = node.add_child(ExpectChangeCipherSpec())
        node = node.add_child(
            ExpectEncryptedExtensions(extensions=expect_exts_ee))
        node = node.add_child(ExpectCertificate())
        node = node.add_child(ExpectCertificateVerify())
        node = node.add_child(ExpectFinished())
        node = node.add_child(FinishedGenerator())
        node = node.add_child(
            ApplicationDataGenerator(bytearray(b"GET / HTTP/1.0\r\n\r\n")))

        # This message is optional and may show up 0 to many times
        cycle = ExpectNewSessionTicket()
        node = node.add_child(cycle)
        node.add_child(cycle)

        node.next_sibling = ExpectApplicationData()
        node = node.next_sibling.add_child(
            AlertGenerator(AlertLevel.warning, AlertDescription.close_notify))

        node = node.add_child(ExpectAlert())
        node.next_sibling = ExpectClose()
        conversations[
            "unassigned extensions with random payload, ids in range from {0} to {1}"
            .format(ext_chunk[0], ext_chunk[-1])] = conversation

    # run the conversation
    good = 0
    bad = 0
    failed = []
    if not num_limit:
        num_limit = len(conversations)

    # make sure that sanity test is run first and last
    # to verify that server was running and kept running throught
    sanity_test = ('sanity', conversations['sanity'])
    ordered_tests = chain([sanity_test],
                          islice(
                              filter(lambda x: x[0] != 'sanity',
                                     conversations.items()), num_limit),
                          [sanity_test])

    for c_name, c_test in ordered_tests:
        if run_only and c_name not in run_only or c_name in run_exclude:
            continue
        print("{0} ...".format(c_name))

        runner = Runner(c_test)

        res = True
        try:
            runner.run()
        except Exception:
            print("Error while processing")
            print(traceback.format_exc())
            res = False

        if res:
            good += 1
            print("OK\n")
        else:
            bad += 1
            failed.append(c_name)

    print("Test with large number of unassigned extensions in TLS 1.3")
    print("Verify that server does not reply to any of these extensions")
    print("and establish regular session.\n")
    print("version: {0}\n".format(version))

    print("Test end")
    print("successful: {0}".format(good))
    print("failed: {0}".format(bad))
    failed_sorted = sorted(failed, key=natural_sort_keys)
    print("  {0}".format('\n  '.join(repr(i) for i in failed_sorted)))

    if bad > 0:
        sys.exit(1)