2
0
mirror of https://github.com/mozilla/cipherscan.git synced 2025-04-21 01:03:39 +02:00
This commit is contained in:
Hubert Kario 2016-10-02 22:45:00 +00:00 committed by GitHub
commit 06f2eb2eb6
18 changed files with 2700 additions and 1 deletions

8
.gitignore vendored
View File

@ -1,2 +1,10 @@
mozilla/* mozilla/*
top1m/results/* top1m/results/*
tlslite
.tlslite-ng
*/__pycache__
*.pyc
.coverage
.python-ecdsa
ecdsa
tlslite

View File

@ -980,6 +980,8 @@ display_results_in_terminal() {
done | sort done | sort
fi fi
fi fi
echo "$cscan_tests"
} }
display_results_in_json() { display_results_in_json() {
@ -1080,7 +1082,12 @@ display_results_in_json() {
echo -n "}" echo -n "}"
ctr=$((ctr+1)) ctr=$((ctr+1))
done done
echo '}}' echo -n '}'
if [[ -n $cscan_tests ]]; then
echo -n ',"intolerancies":'
echo -n "$cscan_tests"
fi
echo '}'
} }
test_serverside_ordering() { test_serverside_ordering() {
@ -1550,6 +1557,17 @@ test_tls_tolerance() {
tls_tolerance['small-SSLv3']="True $current_protocol $current_cipher $current_trusted" tls_tolerance['small-SSLv3']="True $current_protocol $current_cipher $current_trusted"
fi fi
fi fi
# finally run the Python based test to perform more precise scan
if [[ "$OUTPUTFORMAT" == "json" ]]; then
cscan_tests="$($DIRNAMEPATH/cscan.sh -j $TARGET $sni_target)"
else
if [[ $VERBOSE != 0 ]]; then
cscan_tests="$($DIRNAMEPATH/cscan.sh -v --no-header $TARGET $sni_target)"
else
cscan_tests="$($DIRNAMEPATH/cscan.sh --no-header $TARGET $sni_target)"
fi
fi
} }
test_kex_sigalgs() { test_kex_sigalgs() {

467
cscan.py Normal file
View File

@ -0,0 +1,467 @@
# Copyright 2016(c) Hubert Kario
# This work is released under the Mozilla Public License Version 2.0
"""tlslite-ng based server configuration (and bug) scanner."""
from __future__ import print_function
from tlslite.messages import ClientHello, ServerHello, ServerHelloDone, Alert
from tlslite.constants import CipherSuite, \
ExtensionType, AlertLevel
from tlslite.extensions import TLSExtension
import sys
import json
import getopt
import itertools
import copy
from cscan.scanner import Scanner
from cscan.config import Xmas_tree, IE_6, IE_8_Win_XP, \
IE_11_Win_7, IE_11_Win_8_1, Firefox_46, Firefox_42, HugeCipherList, \
VeryCompatible
from cscan.modifiers import no_sni, set_hello_version, set_record_version, \
no_extensions, truncate_ciphers_to_size, append_ciphers_to_size, \
extend_with_ext_to_size, add_empty_ext
from cscan.bisector import Bisect
def scan_with_config(host, port, conf, hostname, __sentry=None, __cache={}):
"""Connect to server and return set of exchanged messages."""
assert __sentry is None
key = (host, port, conf, hostname)
if key in __cache:
return __cache[key]
scanner = Scanner(conf, host, port, hostname)
ret = scanner.scan()
__cache[key] = ret
if verbose and not json_out:
print(".", end='')
sys.stdout.flush()
return ret
class IE_6_ext_tls_1_0(IE_6):
def __init__(self):
super(IE_6_ext_tls_1_0, self).__init__()
self.modifications += ["TLSv1.0", "ext"]
self.version = (3, 1)
self.record_version = (3, 0)
def __call__(self, hostname):
ret = super(IE_6_ext_tls_1_0, self).__call__(hostname)
ret.ssl2 = False
# filter out SSLv2 ciphersuites
ret.cipher_suites = [i for i in ret.cipher_suites if i <= 0xffff and
i != CipherSuite.
TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
ret.extensions = [TLSExtension(extType=ExtensionType.
renegotiation_info)
.create(bytearray(1))]
return ret
def simple_inspector(result):
"""
Perform simple check to see if connection was successful.
Returns True is connection was successful, server replied with
ServerHello and ServerHelloDone messages, and the cipher selected
was present in ciphers advertised by client, False otherwise
"""
if any(isinstance(x, ServerHelloDone) for x in result):
ch = next((x for x in result if isinstance(x, ClientHello)), None)
sh = next((x for x in result if isinstance(x, ServerHello)), None)
if ch and sh:
if sh.cipher_suite not in ch.cipher_suites:
# FAILURE cipher suite mismatch
return False
return True
# incomplete response or error
return False
def verbose_inspector(desc, result):
"""Describe the connection result in human-readable form."""
ret = "{0}:".format(desc)
if any(isinstance(x, ServerHelloDone) for x in result):
ch = next((x for x in result if isinstance(x, ClientHello)), None)
sh = next((x for x in result if isinstance(x, ServerHello)), None)
if sh and ch:
if sh.cipher_suite not in ch.cipher_suites:
ret += " FAILURE cipher suite mismatch"
return ret
name = CipherSuite.ietfNames[sh.cipher_suite] \
if sh.cipher_suite in CipherSuite.ietfNames \
else hex(sh.cipher_suite)
ret += " ok: {0}, {1}".format(sh.server_version,
name)
return ret
ret += " FAILURE "
errors = []
for msg in result:
if isinstance(msg, ClientHello):
continue
# check if returned message supports custom formatting
if msg.__class__.__format__ is not object.__format__:
errors += ["{:vxm}".format(msg)]
else:
errors += [repr(msg)]
# skip printing close errors after fatal alerts, they are expected
if isinstance(msg, Alert) and msg.level == AlertLevel.fatal:
break
ret += "\n".join(errors)
return ret
configs = {}
def load_configs():
"""Load known client configurations for later use in scanning."""
base_configs = [Xmas_tree, Firefox_42, IE_8_Win_XP, IE_11_Win_7,
VeryCompatible]
for conf in base_configs:
# only no extensions
gen = no_extensions(conf())
configs[gen.name] = gen
gen = no_sni(conf())
configs[gen.name] = gen
for version in ((3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 254)):
if conf().version != version:
# just changed version
gen = set_hello_version(conf(), version)
if gen.record_version > version:
gen = set_record_version(gen, version)
configs[gen.name] = gen
# changed version and no extensions
gen = set_hello_version(conf(), version)
if gen.record_version > version:
gen = set_record_version(gen, version)
gen = no_extensions(gen)
configs[gen.name] = gen
# changed version and no sni
gen = set_hello_version(conf(), version)
if gen.record_version > version:
gen = set_record_version(gen, version)
gen = no_sni(gen)
configs[gen.name] = gen
# Xmas tree configs
gen = Xmas_tree()
configs[gen.name] = gen
gen = no_sni(Xmas_tree())
configs[gen.name] = gen
# Firefox 42 configs
gen = Firefox_42()
configs[gen.name] = gen
# Firefox 46 configs
gen = Firefox_46()
configs[gen.name] = gen
gen = set_hello_version(Firefox_46(), (3, 254))
configs[gen.name] = gen
gen = set_hello_version(Firefox_46(), (3, 5))
configs[gen.name] = gen
gen = no_extensions(set_hello_version(Firefox_46(), (3, 5)))
configs[gen.name] = gen
gen = set_hello_version(Firefox_46(), (3, 1))
configs[gen.name] = gen
# IE 6 configs
gen = IE_6()
configs[gen.name] = gen
gen = IE_6_ext_tls_1_0()
configs[gen.name] = gen
# IE 8 configs
gen = IE_8_Win_XP()
configs[gen.name] = gen
gen = extend_with_ext_to_size(IE_8_Win_XP(), 200)
configs[gen.name] = gen
for ext_id in (0, 1, 2, 3, 4, 5):
gen = add_empty_ext(IE_8_Win_XP(), ext_id)
configs[gen.name] = gen
# IE 11 on Win 7 configs
gen = IE_11_Win_7()
configs[gen.name] = gen
gen = no_sni(IE_11_Win_7())
configs[gen.name] = gen
gen = set_hello_version(no_sni(IE_11_Win_7()), (3, 2))
configs[gen.name] = gen
# IE 11 on Win 8.1 configs
gen = IE_11_Win_8_1()
configs[gen.name] = gen
gen = set_hello_version(IE_11_Win_8_1(), (3, 1))
configs[gen.name] = gen
gen = set_hello_version(IE_11_Win_8_1(), (3, 4))
configs[gen.name] = gen
# Huge Cipher List
gen = HugeCipherList()
configs[gen.name] = gen
gen = truncate_ciphers_to_size(HugeCipherList(), 16388)
configs[gen.name] = gen
# Very Compatible
gen = VeryCompatible()
configs[gen.name] = gen
gen = append_ciphers_to_size(VeryCompatible(), 2**16)
configs[gen.name] = gen
gen = extend_with_ext_to_size(VeryCompatible(), 2**16)
configs[gen.name] = gen
gen = extend_with_ext_to_size(VeryCompatible(), 16388)
configs[gen.name] = gen
def scan_TLS_intolerancies(host, port, hostname):
"""Look for intolerancies (version, extensions, ...) in a TLS server."""
results = {}
def result_iterator(predicate):
"""
Selecting iterator over cached results.
Looks for matching result from already performed scans
"""
return (not simple_inspector(results[name]) for name in results
if predicate(configs[name]))
def result_cache(name, conf):
"""Perform scan if config is not in results, caches result."""
return results[name] if name in results \
else results.setdefault(name, scan_with_config(host, port, conf,
hostname))
def conf_iterator(predicate):
"""
Caching, selecting iterator over configs.
Returns an iterator that will go over configs that match the provided
predicate (a function that returns true or false depending if given
config is ok for test at hand) while saving the results to the
cache/verbose `results` log/dictionary
"""
scan_iter = (not simple_inspector(result_cache(name, conf))
for name, conf in configs.items()
if predicate(conf))
return itertools.chain(result_iterator(predicate), scan_iter)
host_up = not all(conf_iterator(lambda conf: True))
intolerancies = {}
if not host_up:
if json_out:
print(json.dumps(intolerancies))
else:
print("Host does not seem to support SSL or TLS protocol")
return
intolerancies["SSL 3.254"] = all(conf_iterator(lambda conf:
conf.version == (3, 254)))
intolerancies["TLS 1.4"] = all(conf_iterator(lambda conf:
conf.version == (3, 5)))
intolerancies["TLS 1.3"] = all(conf_iterator(lambda conf:
conf.version == (3, 4)))
intolerancies["TLS 1.2"] = all(conf_iterator(lambda conf:
conf.version == (3, 3)))
intolerancies["TLS 1.1"] = all(conf_iterator(lambda conf:
conf.version == (3, 2)))
intolerancies["TLS 1.0"] = all(conf_iterator(lambda conf:
conf.version == (3, 1)))
intolerancies["extensions"] = all(conf_iterator(lambda conf:
conf.extensions and not
conf.ssl2))
#for name in ["Xmas tree", "Huge Cipher List",
# "Huge Cipher List (trunc c/16388)"]:
# intolerancies[name] = all(conf_iterator(lambda conf:
# conf.name == name))
def test_cb(client_hello):
ret = scan_with_config(host, port, lambda _:client_hello, hostname)
return simple_inspector(ret)
# most size intolerancies lie between 16385 and 16389 so short-circuit to
# them if possible
good_conf = next((configs[name] for name, result in results.items()
if simple_inspector(result)), None)
if good_conf:
size_c_16382 = simple_inspector(scan_with_config(host, port,
append_ciphers_to_size(copy.deepcopy(good_conf), 16382), hostname))
size_c_16392 = simple_inspector(scan_with_config(host, port,
append_ciphers_to_size(copy.deepcopy(good_conf), 16392), hostname))
if size_c_16382 and not size_c_16392:
good = append_ciphers_to_size(copy.deepcopy(good_conf), 16382)
bad = append_ciphers_to_size(copy.deepcopy(good_conf), 16392)
elif not size_c_16382:
good = good_conf
bad = append_ciphers_to_size(copy.deepcopy(good_conf), 16382)
else:
bad = append_ciphers_to_size(copy.deepcopy(good_conf), 65536)
size_c_65536 = simple_inspector(scan_with_config(host, port,
bad, hostname))
if not size_c_65536:
good = None
intolerancies["size c/65536"] = False
else:
good = append_ciphers_to_size(copy.deepcopy(good_conf), 16392)
if good:
bisect = Bisect(good, bad, hostname, test_cb)
good_h, bad_h = bisect.run()
intolerancies["size c/{0}".format(len(bad_h.write()))] = True
intolerancies["size c/{0}".format(len(good_h.write()))] = False
# test extension size intolerance, again, most lie between 16385
# and 16389 so short-circuit if possible
good_conf = next((configs[name] for name, result in results.items()
if configs[name].extensions and
simple_inspector(result)), None)
if good_conf:
size_e_16382 = simple_inspector(scan_with_config(host, port,
extend_with_ext_to_size(copy.deepcopy(good_conf), 16382), hostname))
size_e_16392 = simple_inspector(scan_with_config(host, port,
extend_with_ext_to_size(copy.deepcopy(good_conf), 16392), hostname))
if size_e_16382 and not size_e_16392:
good = extend_with_ext_to_size(copy.deepcopy(good_conf), 16382)
bad = extend_with_ext_to_size(copy.deepcopy(good_conf), 16392)
elif not size_e_16382:
good = good_conf
bad = extend_with_ext_to_size(copy.deepcopy(good_conf), 16382)
else:
bad = extend_with_ext_to_size(copy.deepcopy(good_conf), 65536)
size_e_65536 = simple_inspector(scan_with_config(host, port,
bad, hostname))
if not size_e_65536:
good = None
intolerancies["size e/65536"] = False
else:
good = extend_with_ext_to_size(copy.deepcopy(good_conf), 16392)
if good:
bisect = Bisect(good, bad, hostname, test_cb)
good_h, bad_h = bisect.run()
intolerancies["size e/{0}".format(len(bad_h.write()))] = True
intolerancies["size e/{0}".format(len(good_h.write()))] = False
if json_out:
print(json.dumps(intolerancies))
else:
if not no_header:
if verbose:
print()
print("Host {0}:{1} scan complete".format(host, port))
if hostname:
print("SNI hostname used: {0}".format(hostname))
if verbose:
print()
print("Individual probe results:")
for desc, ret in sorted(results.items()):
print(verbose_inspector(desc, ret))
print()
print("Intolerance to:")
for intolerance, value in sorted(intolerancies.items()):
print(" {0:20}: {1}".format(intolerance,
"PRESENT" if value else "absent"))
def single_probe(name):
"""Run a single probe against a server, print result."""
print(verbose_inspector(name, scan_with_config(host, port,
configs[name], hostname)))
def usage():
"""Print usage information."""
print("./cscan.py [ARGUMENTS] host[:port] [SNI-HOST-NAME]")
print()
print("-l, --list List probe names")
print("-p name, --probe Run just a single probe")
print("-j, --json Output in JSON format")
print("-v, --verbose Use verbose output")
if __name__ == "__main__":
try:
opts, args = getopt.getopt(sys.argv[1:],
"jvhlp:",
["json", "verbose", "help", "list",
"probe=", "no-header"])
except getopt.GetoptError as err:
print(err)
usage()
sys.exit(2)
json_out = False
verbose = False
list_probes = False
run_probe = None
no_header = False
for opt, arg in opts:
if opt in ('-j', '--json'):
json_out = True
elif opt in ('-v', '--verbose'):
verbose = True
elif opt in ('-h', '--help'):
usage()
sys.exit(0)
elif opt in ('-l', '--list'):
list_probes = True
elif opt in ('-p', '--probe'):
run_probe = arg
elif opt in ('--no-header', ):
no_header = True
else:
raise AssertionError("Unknown option {0}".format(opt))
if len(args) > 2:
print("Too many arguments")
usage()
sys.exit(2)
load_configs()
if list_probes:
for desc, ret in sorted(configs.items()):
print("{0}: {1}".format(desc, ret.__doc__))
sys.exit(0)
hostname = None
if len(args) == 2:
hostname = args[1]
hostaddr = args[0].split(":")
if len(hostaddr) > 1:
host, port = hostaddr
else:
host = hostaddr[0]
port = 443
if run_probe:
single_probe(run_probe)
else:
scan_TLS_intolerancies(host, port, hostname)

26
cscan.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
pushd "$(dirname ${BASH_SOURCE[0]})" > /dev/null
if [ ! -d ./tlslite ]; then
git clone --depth=1 https://github.com/tomato42/tlslite-ng.git .tlslite-ng
ln -s .tlslite-ng/tlslite tlslite
fi
if [ ! -d ./ecdsa ]; then
git clone --depth=1 https://github.com/warner/python-ecdsa.git .python-ecdsa
ln -s .python-ecdsa/ecdsa ecdsa
fi
# update the code if it is running in interactive terminal
#if [[ -t 1 ]]; then
if [[ $UPDATE ]]; then
pushd .tlslite-ng >/dev/null
git pull origin master --quiet
popd >/dev/null
pushd .python-ecdsa >/dev/null
git pull origin master --quiet
popd >/dev/null
fi
PYTHONPATH=. python cscan.py "$@"
ret=$?
popd > /dev/null
exit $ret

0
cscan/__init__.py Normal file
View File

172
cscan/bisector.py Normal file
View File

@ -0,0 +1,172 @@
# Copyright (c) 2015 Hubert Kario <hkario@redhat.com>
# Released under Mozilla Public License Version 2.0
"""Find an itolerance through bisecting Client Hello"""
import copy
from tlslite.extensions import PaddingExtension
def list_union(first, second):
"""Return an union between two lists, preserving order"""
first_i = iter(first)
second_i = iter(second)
first_s = set(first)
second_s = set(second)
ret = []
first_el = next(first_i, None)
second_el = next(second_i, None)
while first_el is not None and second_el is not None:
if first_el != second_el:
if first_el in second_s and second_el in first_s:
# the second list is longer, so take from it
ret.append(second_el)
# no discard as we would have duplicates
second_el = next(second_i, None)
continue
if first_el not in second_s:
ret.append(first_el)
first_s.discard(first_el)
first_el = next(first_i, None)
if second_el not in first_s:
ret.append(second_el)
second_s.discard(second_el)
second_el = next(second_i, None)
else:
ret.append(first_el)
first_s.discard(first_el)
second_s.discard(first_el)
first_el = next(first_i, None)
second_el = next(second_i, None)
while first_el:
if first_el not in second_s:
ret.append(first_el)
first_el = next(first_i, None)
while second_el:
if second_el not in first_s:
ret.append(second_el)
second_el = next(second_i, None)
return ret
def bisect_lists(first, second):
"""Return a list that is in the "middle" between the given ones"""
# handle None special cases
if first is None and second is None:
return None
if first is not None and second is None:
first, second = second, first
if first is None and second is not None:
if len(second) == 0:
return None
elif len(second) == 1:
return []
else:
first = []
# make the second lists always the longer one
if len(first) > len(second):
second, first = first, second
first_s = set(first)
second_s = set(second)
union = list_union(first, second)
symmetric_diff = first_s.symmetric_difference(second_s)
# preserve order for the difference
symmetric_diff = [x for x in union if x in symmetric_diff]
half_diff = set(symmetric_diff[:len(symmetric_diff)//2])
intersection = first_s & second_s
return [x for x in union if x in half_diff or x in intersection]
def bisect_padding_extension(first, second):
if first is None and second is None:
return None
if first is not None and second is None:
first, second = second, first
if first is None and second is not None:
if len(second.paddingData) == 0:
return None
elif len(second.paddingData) == 1:
return PaddingExtension()
else:
first = PaddingExtension()
return PaddingExtension().create((len(first.paddingData) +
len(second.paddingData)) // 2)
def bisect_extensions(first, second):
# handle padding extension
if first is None and second is None:
return None
if first is not None and second is None:
first, second = second, first
if first is None and second is not None:
if len(second) == 0:
return None
if len(second) == 1:
return []
first = []
f_ext = next((x for x in first if isinstance(x, PaddingExtension)), None)
s_ext = next((x for x in second if isinstance(x, PaddingExtension)), None)
ext = bisect_padding_extension(f_ext, s_ext)
if ext is None:
# remove the extension
return [x for x in first if not isinstance(x, PaddingExtension)]
else:
if f_ext is None:
return first + [ext]
# replace extension
return [ext if isinstance(x, PaddingExtension) else x for x in first]
def bisect_hellos(first, second):
"""Return a client hello that is in the "middle" of two other"""
ret = copy.copy(first)
ret.client_version = ((first.client_version[0] + second.client_version[0])
// 2,
(first.client_version[1] + second.client_version[1])
// 2)
ret.cipher_suites = bisect_lists(first.cipher_suites, second.cipher_suites)
ret.extensions = bisect_lists(first.extensions, second.extensions)
ret.compression_methods = bisect_lists(first.compression_methods,
second.compression_methods)
if first.extensions == ret.extensions \
or second.extensions == ret.extensions:
ret.extensions = bisect_extensions(first.extensions,
second.extensions)
return ret
class Bisect(object):
"""
Perform a bisection between two Client Hello's to find intolerance
Tries to find a cause for intolerance by using a bisection-like
algorithm
"""
def __init__(self, good, bad, hostname, callback):
"""Set the generators for good and bad hello's and callback to test"""
self.good = good
self.bad = bad
if hostname is not None:
self.hostname = bytearray(hostname, 'utf-8')
else:
self.hostname = None
self.callback = callback
def run(self):
good_hello = self.good(self.hostname)
bad_hello = self.bad(self.hostname)
middle = bisect_hellos(good_hello, bad_hello)
while good_hello != middle and \
middle != bad_hello:
if self.callback(middle):
good_hello = middle
else:
bad_hello = middle
middle = bisect_hellos(good_hello, bad_hello)
return (good_hello, bad_hello)

580
cscan/config.py Normal file
View File

@ -0,0 +1,580 @@
# Copyright (c) 2016 Hubert Kario <hkario@redhat.com>
# Released under Mozilla Public License Version 2.0
"""Typical Client Hello messages sent by different clients."""
import random
from tlslite.messages import ClientHello
from tlslite.constants import \
ECPointFormat, HashAlgorithm, SignatureAlgorithm
from tlslite.mathtls import goodGroupParameters
from tlslite.extensions import SNIExtension, SupportedGroupsExtension, \
TLSExtension, SignatureAlgorithmsExtension, NPNExtension, \
ECPointFormatsExtension, PaddingExtension
from tlslite.utils.cryptomath import numberToByteArray
from tlslite.utils.ecc import getCurveByName, encodeX962Point
from tlslite.utils.cryptomath import powMod
from .constants import CipherSuite, ExtensionType, GroupName
from .extensions import KeyShareExtension
class HelloConfig(object):
"""Base object for all Client Hello configurations."""
def __init__(self):
"""Initialize object with default settings."""
self._name = None
self.modifications = []
self.callbacks = []
self.version = (3, 3)
self.record_version = (3, 0)
self.ciphers = []
self.extensions = None
self.random = None
self.session_id = bytearray(0)
self.compression_methods = [0]
self.ssl2 = False
@property
def name(self):
"""Return the name of config with all the modifications applied."""
if self.modifications:
return "{0} ({1})".format(self._name,
", ".join(self.modifications))
else:
return self._name
@name.setter
def name(self, value):
"""Set the base name of the configuration."""
self._name = value
def __call__(self, hostname):
"""Generate a client hello object, use hostname in SNI extension."""
# SNI is special in that we don't want to send it if it is empty
if self.extensions:
sni = next((x for x in self.extensions
if isinstance(x, SNIExtension)),
None)
if sni:
if hostname is not None:
if sni.serverNames is None:
sni.serverNames = []
sni.hostNames = [hostname]
else:
# but if we were not provided with a host name, we want
# to remove empty extension
if sni.serverNames is None:
self.extensions = [x for x in self.extensions
if not isinstance(x, SNIExtension)]
if self.random:
rand = self.random
else:
# we're not doing any crypto with it, just need "something"
# TODO: place unix time at the beginning
rand = numberToByteArray(random.getrandbits(256), 32)
ch = ClientHello(self.ssl2).create(self.version, rand, self.session_id,
self.ciphers,
extensions=self.extensions)
ch.compression_methods = self.compression_methods
for cb in self.callbacks:
ch = cb(ch)
return ch
class Firefox_42(HelloConfig):
"""Create Client Hello like Firefox 42."""
def __init__(self):
"""Set the configuration to Firefox 42."""
super(Firefox_42, self).__init__()
self._name = "Firefox 42"
self.version = (3, 3)
self.record_version = (3, 1)
self.ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA]
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(TLSExtension(extType=ExtensionType.renegotiation_info)
.create(bytearray(1)))
ext.append(SupportedGroupsExtension().create([GroupName.secp256r1,
GroupName.secp384r1,
GroupName.secp521r1]))
ext.append(ECPointFormatsExtension()
.create([ECPointFormat.uncompressed]))
ext.append(TLSExtension(extType=ExtensionType.session_ticket))
ext.append(NPNExtension())
ext.append(TLSExtension(extType=ExtensionType.alpn)
.create(bytearray(b'\x00\x15' +
b'\x02' + b'h2' +
b'\x08' + b'spdy/3.1' +
b'\x08' + b'http/1.1')))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
sig_algs = []
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.rsa))
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.ecdsa))
for alg in ['sha256', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension()
.create(sig_algs))
class Firefox_46(HelloConfig):
"""Create ClientHello like Firefox 46"""
# verified by packet capture
def __init__(self):
super(Firefox_46, self).__init__()
self._name = "Firefox 46"
self.version = (3, 3)
self.record_version = (3, 1)
self.ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA]
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(TLSExtension(extType=ExtensionType.extended_master_secret))
ext.append(TLSExtension(extType=ExtensionType.renegotiation_info)
.create(bytearray(1)))
ext.append(SupportedGroupsExtension().create([GroupName.secp256r1,
GroupName.secp384r1,
GroupName.secp521r1]))
ext.append(ECPointFormatsExtension().create([ECPointFormat.uncompressed]))
ext.append(TLSExtension(extType=ExtensionType.session_ticket))
ext.append(NPNExtension())
ext.append(TLSExtension(extType=ExtensionType.alpn)
.create(bytearray(b'\x00\x15' +
b'\x02' + b'h2' +
b'\x08' + b'spdy/3.1' +
b'\x08' + b'http/1.1')))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
sig_algs = []
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.rsa))
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.ecdsa))
for alg in ['sha256', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension()
.create(sig_algs))
_ec_precomputed = {
'secp256r1' : [
bytearray(b'\x04\x85\x96\xe5\xa1\x92\x99\xf8\xb1\xb1}\xcd\xdc\x1c^\xfd05\xa1\xe1\xd3\xd0\x87s\xb1\xd8>\x00TX\xac\x86\xe1\xa3\xb0c~\x85\x8d&\xaf"X\xb5\x9cf\x8d\xf4\xf5d\xd7u\'&]\xbe\xe3\x83]ul\xff\x86e\x89'),
bytearray(b'\x04\xa89\xfc\xb2r\xf3\x03p6\xca\xb6\xe2\xb1t2\t\x8b\xb2\x89\xce\x8f\x84\r\x8b\x1b\r\\\r/\xcd\xabn\xb6WE\xb7\x9a\xba\r\xdb\x03\xc3\x9c\xd9N\x19\x03[\xe0\nK\xf3\xfb\x00\xbe]d\x96e\xf2\xde\x94\td'),
bytearray(b'\x04\xaa\xf1Q\xfb\xf1\x95\x03\xaa\x8d\x8c\xefB\x8c;\x04u\xdaG\xee\xe5\xd9`J\xe3\x90\xc3T\x02\xe7\x80\x9b\x0f,bi4m2\n\xffD\x9b\xb68\x10\xa3\xbdd]\x05\xa3\x81\xfe\x97J\x9aY\x0e\xe3\x1b\xad\xe4\x84\xbd'),
bytearray(b'\x04\x17\xf9K\x1b\x95\xd3\xf7\xfc`y\xe7g*A\xeb4!\xbca\xd1)\xa0\x0e\xa88\xbd\xb0\xd6\x9a\x97\x95\xa1R2\xcc]$[\xc1\x90T\x83Lu\xbb%I\x81\x8a\x11\x0c\x01\x07)"\x80\xbb\xbaS\xa5`r\x91\xe6'),
bytearray(b"\x04xGX\x06\xb2N\xe2\x9f\xfc\xdc}\xd2\xdbU\x9a$\xb6\xbc`\xb6\xec\xaa\xad(\xa4\x7fwU\xcfK/T\xea\xa5\xc2Y\xf7<\xfe\xa9\xaf\xa2f7\xa2\'/\xfd\x18\xae\x1e\xc0,\xa5cd\x8d2\x98\xeeA#\x13\x9c"),
bytearray(b'\x04\x14\xf0\xf5_\xa1\xf6\xa5\x1c\xf4%\x17\xc4\x86uE|\xc3#\x11\x9b\xf8\xf2\xcc\xc8F/\xcb\xc46,\x81+@\xbeS\x93\xa59\xae\xc8\xe9 {\r\xdfE j!\xa7\xef\x85\x04\xa0\x9bI\xbbiC\x9c#II\xb2')],
'secp384r1' : [
bytearray(b'\x04*\x9c\x14\xfe!_\xea\xa7\xef\x8aR\xd7\x9d\xab\xc3fU6)\x8b\xce\x935q\x92l\xbc\xb84\x96$\xf0\x86\x05\x85\xaaEq\xca0\x99\xf6c\xf2+=\xc9!Ms%z\xf7\x98\x1auCOb\xcc\xdc\x89\x1d\x97\xa7y\xe0\x8e\xea/\x80brj\xd7\x1b\x17\x89<\xe5\xba\xa3*\x0c\xb5\r\xde}\xa7{\x94\x9f\x81\xb3:\x92'),
bytearray(b"\x04\x9b]\xfap\xf5R\xefCv\x95\xc4c\xea\xee\xe1i\xf80\x97\x08\xdb\xe7\x83a\x8f~`\xd7`>A\x8aS6\x82\'\xd6711\x9b,+\xc9\xbb\xa7\xa3E\xe3\xfb\x1a\xa2\x83\xac\xf8\x0eV\x93y\xb2w\xe5\xf5hQ3H\xa0\xa9\x8d\xfdg\xe9\xac\x12\xcd%(bY\xc5+\x98o\xb5D1\xcbu\x7f\t\x8f\xfe\xfa\x16q"),
bytearray(b'\x04@\'b\'{\x15H\xb0\x9c?\xe5dg\xbf\xe4\'\xe1D\xf7FIs\xb9\x153\x87x\xcb\xb7\xf6b\x9b\xe5\x10_\xfc%Z\x87\xa5\xae(\x13\xb1*]_g\xd4\xeaeH\x83\xed\xa0N\x82Mqz;\xbba\x0f\xf6\x9c\x08"\xca\x00\x8fq\xc5\xc7\t{\xf1l_h\x05\xec\x9a\x19N\x98+%=?\x14\xbf0\x94\x13"'),
bytearray(b"\x04pM\xfe\xf0+h\xe3\x9d\xda\xe1x1\x02}\xa4\xa2\x83\'\xfem\x1a;\xbf\x10\xd7\xb2\xd9I\x06-\x9fY\xa3k9\xc4\xc8\xda\x1e>G\xaf\xcb|\x01\xd6j\xff|\xe0>\x81I\x8b\x03\xd3\xf8\xc9|\xab&w\xf8\x1b\x9a\x98\xb1CXAU\t\x03\xd6\xcf\xa0L\xd1B \x13Mr\xbe\x8b\xe6}\xf2\xb2oc\x98A\x94\x9eX"),
bytearray(b'\x04\x1a[X\x13\x17"^\x02\x19d\xc4\xec\x14\xea<\x8c\xd2f\x95\xb4b\xdc\xbbG;pq\x9a^\xf8\xd0\xe7\x99Ofk\x1e#\x8dZ\xcb\xbf\x1e=p\xaa\xe5\x92\xda^\xb2\x86\xdf\xf8(\xfcIc\xde\xcempP\x82\xcd\x88\xe3\x87\xd1\x8b\xac\xe5\x19\xd4)\ndPX\\)\xf7\x1c\xdef\x0e` \x13\xa1\xa8YSun4'),
bytearray(b"\x04\xce\xbe\xb9{$#\x03c\xdemi\xf0\xec\x07]Q8^\x8b\xdd,)~\x972]cI\xbe\xc9\x8b\xa0\xd7\xc7\x96H\xa4\xea\xb2\x86%\xee\xb2\xc8\x08\x1d\xa4\xb9\xc5Bc \xac-;J{\x10\xafh\xceU*+h\xda&7\'\x15<x\xa8\xb9\xbecR\x97\xff<\xb2\xba\x92!\xc6|\xb1\xb5\x01\xb5c\xc9|\xe7\xbd\x01")],
'secp521r1' : [
bytearray(b"\x04\x01\xf9\xe3\x88\xcdU\xeb\xda.3\x8b\xf8\xc2\x82M\xd9\xf1@\x9d\xa6\xee\xa4\xc7\x82\xbf\x81S\xce\xedQ\xb4o,(>\xb7d5\xfd\x94\x86\xd3\xe8=\xcc\xbf\x8d\x07\x17\xc9(\x17U@gH\x8d]\xc5\xe2\xe2;.S\xf9\xa1\x00\xc2\xb5\xb7\'\x05f\xe6\xf3\xf7Za\xa1\xa4\x9209\xf3I\xe3\xdd%\xd1\x8a\x82\n4Z\xa1ol?l(\x939_+(\x1c\xca\xa4\xc3\xdb\x01\x19\xc5\xb4\xfc\xb7\xa8\xa4\xd3\x14x\x0c\x1f\x91\xd1!5\x1a\xb0\x0b\xa7\xe9"),
bytearray(b'\x04\x01\xban\x80\x85\xe8\xc7\xd0r\x17\x84\xea\x16\xc0\xfdM\xde\x98\xfe\x82\xd9\x06{\t\x1f*\xa2(t\xd4\x95y\xb7\x98\xbbP\xf1\xd9_9\xe3\xd3\xf8\xf1\xa9\xec\x1c\xce\xf6\\\xb1\xb6`|\xb9`]Wy\xaf\x99\xfc\xc2\xa5$\xd9\x01\xb5Y\x85\x85.\xc8\x8a\x94\xde\xbc\xe6m/\x04C 6|m\x92\x00\xfan*\x1c\xb7z\xbc\xc4|\xea,/t\xc7,"<R5h\x95\xf9\xc3%\x87\xa6!\x97\xa9\x94\xc7\t=\x1d\x8b,\xb6\xb5\xafF_r\\V'),
bytearray(b'\x04\x01\xf5?\xbf\x065\x9bB}\xdb\x90\xadD\xd6\xe2?e\xae\xb9\x13\xa8zZ7\xa0\xa3\xf1}b3\\\x08\x81d~V\\;\xd2\x9a\xc9\x93\x84-e\'\x9cy\xc1t\xaa\xce\x0f\x9a\xfdk\x1c9\xf6R\xeb\xa1\xf4kw\x10\x01P)]\xfb\xc7n\xb7\xfe\xd8\xdaO\xc9DK\xbb\xb4/\xfb\x17J\x98\xaen\xd4\x93\x16n\xb1\x95\x94\xe5\xc6\xfd\x0bADj\x0e\xc1\xf5\x81`\xc8\xe9;X\ttr\xb1.\x96f\xe2\xc9^?\xf7r=\xf2"\x9f}<'),
bytearray(b'\x04\x01g3\xe2\x1f\x8b|$#V\xa0\x05\xd0=\xc6\xdf\xd9\xaf\xf2&\xebEn\xf6?9\xfaQ\xfb\x94\x8be3\x1f\xb3\x91\xa5|3\x981\x8d\x1d\x92QL\xd6\xa3\x80o++9\xaf#cN\xe8\x19O\x84+\x8e\x1cT\xf8\x00\xca\xe2\x01\xbc\xd9\xa4\xccV\xa0\x1d\xb7\xe7\xecm{\xa9<\xc31o&\xfaq\xba\x1av\x80\x1c\x06\xd3\xad\x95-\x9d\x03\x1e\x97}\xd2??\xb8\xb3r\x94\xd1\xba\x9aC\x95\xe2\xab\xd1\xe3V2\x0f\xbe\xce\xdb\xbamT\xf7\xd3'),
bytearray(b'\x04\x01~\xea\x9a?\xc3[\x99\x8f\xa0z\x1a\x95\xa8T\xd6Y\xc9\x90\xbe\\!\x82\xd2\x04"/\xe7\xf1\xb9,\x7f~\x12\xc3\xe4\xcd\x9f\xf0.!\x1c?2v\x0b\xa7\x04\xe64\x8c\x1a2V\x91\x07\xe2\xc8\x182\xc6\x9b\x9e\xe8\x02\xc1\x00\x05\xfd\xc5\x16S\xa4\x97Ds\x95\xb4Md\xd79\x1cn\xdfLC\'\x02\xcf\x9f\xdb\xa8\xd5\x88\x99\xb3/\xb4M\x8fN\xcf\x81\xeb\x97z\x05\x17\xb4\xcdb\x86\x86\x84\\\xe1Y\x96\xf09\x02\xa79S9\x15\x91\xce\xd84g'),
bytearray(b'\x04\x01\xbc\xcd7\xb4\x03\xfa.\x0e\x01\xf7\x13\xa4e\xd9\xfd\xcb\xb9\xfb\x8c\xf5\x1e\x1b\xafU\xedN\x00C\xe7b\xcf\x0c\xcd"\x9a\xec\xec\xf6\xce\xbf\x9a\x9d&\x12\x03\x05T,\x98*\xbc0g\xf5\x9e{/B\xdd5dv\xafNs\x01\xe0\x8d\xf9\x8b\x92\xa0\x9f\xf3\xf8oi\xba\xe8(\xc9\xa2R\x8b\xf9C\xa1):A"\xec\x80\xf5\x94\xb2\xd7\x1d\xc0\x9eU\x10\x18\xd9\xc6 i\\0\xb1\xc5\xc1S\xac\x94< \xb1\xe7\xf0L[\x02T\xde\xac0\xb90\xa4\x05')]
}
def _ec_key_share(name):
if name in _ec_precomputed:
return random.choice(_ec_precomputed[name])
generator = getCurveByName(name).generator
secret = random.randint(2, generator.order())
ret = encodeX962Point(generator * secret)
return ret
_ff_precomputed = {
'ffdhe8192' : [
bytearray(b'\xea8\xe0\xccGf\x1c)\xb9\xf2p\x87\\s\xb2\xa2\xa3\xb3F#\x1a\x92@\x92@1]D\x9b\xf4\xb5g\x02\x86M\xa8 *\xa0\xb2P9\xbf!v\xca\xdb\xfb\xaf\xf9\x1fn\x8f.\x1b,\xff8\x04\\Vn\xd3\xcdM\xfa\x10\xfeh\xd7u\xf5j\xf8\xc9\x08F\xe7\x98P\xa4n\xea\xa0\x81W\xf0@4M\\\x84j\x87\x1e\xe37{)\\\xd5\xb3\xcfm-]\xd8\x9d\xc6\x17\xb4,\xe1:\xd6\xbc\xb52Am\xcf\x87\x020\xf0*2\xb4\xc6\x03\xfd\x01\xd6s\xf7\xe9\xf3\x01I\xc2m\xaf\xd6\x98\x9edg\xeax\x14\x0e8V0Je)\xe2j\xcc\x89\xf5K\xc5\xaa\x0c\x08m&0\xda:\x08fZ\xcbh\x1fkY&\xb9\xca\xd7\x7f\xb6w\x1d}Z4T^\x00\x058\x0b\xe3\xcf\x10\xdb\x1a\x10\xfe\xce\xd1f\xeaT\xf1\x15\x13\x02\xe1\x95 \xc7\xc1\xa6\xb8\xad\x18]\x1d0\x86@\xf2\xf6Q;IT\xbbs\x0eU\x95O\xbc\'\xa4w\xb3\xf6\xdcQ\xc0\xd57H\xe4L\xfe61\x9c]\x13\x0f\r\x8b\x80\x05~N\xba+/\xa7Y*\xf2\xa4\xad\x14Xl)\xef\xac\x96\xc9\x04o\x9a\x950\xac\xb09\x08\x1b\r.\xed9\x92\xbd\x15\x92l8\xa9\xd6\xb0\x99Z>\xe2\x8b\xe7R\xb6\xc1IC\x12\x1f\x1f7Z\xdf\x1e\x04\xeb-@\xa2\x1e\xb4E\x83\xe8\xfe\xba\xbb3la\xa6\x8e\xf7\x8e\xd7\xa7Q\x81\x9f|(\xe7\xf6\xc0\x96\xdf\x01\x945\xb0\xdd=\x99\xde\xa4\xf1}(4\xe8\xf1L\xb5\x88\xb2\x9a\xde\x12\xcfn\'\xce\xb8g\x89\x0eY\x9c\x8a6-i\xafO\x0f\xbfY\x8b\xb3T\x0f"g3\xdf%r\xbakOV\xcd\xc5\x1f&\xa7\x1e$\x8c`IV\xc3\x1af\xbar\xfb2\xe1\xfe{k,\xb0\xe8{\x17\x9b\xd8\xccrM\x17&4\xd1\xad*fM\x9b\xfetW\xafU\x1bDYCW\xb25O`/p\xb7\xf4\xcb\xedI\xf9\x17~\xa7\x13\x18\xb9Vv\xb4\xa3\xe4\xde\xe8\x16\x8a\x8d%\xf3Cc\x977\x17\x947\xd7%\xac-0\xbeX@\xf2o\xad\xf1w\xa3N\xf9}\x13\xeb\xc7\x97T3\x008\xa5\xc1\x1c\xc4w\x91F\xd3L\x9eY\xd1\x89\xbdc\n\'\x80\x80\x8f\x18\xee#\xdc[\xac\xae\xe50\xdc\xa2\xec\x9c\xf1\rO\x95\xe4\x06\x8b5\xb1\xb3s\'4M\x11u\xfd\x87\xc0\x19\xa0\x0b\xc6=\xe5\xb4t\xe7\x19\x19h\xd9<w\xf4Ql\'\x99\x13\xceoD3\xa2^\xee\x94\x04V\xde\x1b\x98\xeb\x0e\xdd\x1a\x85\xf2\xc1H*\x19\x16+Z\xb2=\xb1\xbe\x0c\xb1\x90T\xc1\xbakv\xc3\xeb\x03\x85\x80\x06Np\x06 \x84\xcc\xcc\x98\xe3W\x8f$\x0e-\xd3\x1b\xb9~k\xdb\xb9\x07.\xfe\xff\x11F\xf8uN\x9a\xf7?Z\xb8\xc0\xe5\xfcP\xa7\xdc\xdb\xe9\xc4\xf1o@\xb7\xe6t\xf3\xdd\x17O\xe7a6\x97<\xc7\xedM\x97\xb7\nx\x03Z\x0f\xf2:\x15B\x07!lW\xdf\x84\xfa\n(&\xbep\xa6\x19\x1eC\x8db\x0f\x19\x85\xfbsV\xa7\x19\xf8j\xc4\x91==s\xb3\t\x00\xaf:\xc9\x86\x06zz\x80\xe7\nd\x7fwd(\xe0!\x8e %\x1d\xfcX{\x94/+\x06\x07F\xdf\x81\xe0.q\x91\xc3\x0fb\xc4\xfcI\xe7\x9a+$e(\xdbu\xd1|\x15\x10f\x14\x1f%KmR\xb0\tE\xc7\xad\xe2\xb6H\x98\x8b\xf0\x0bL{\xcbp\x16\xd5\xb5\xf1u\xc4\x801l\xd4M\x7f\x92\xe6\xbf}\x17\xe0\xc0^8U\x15~\xca\xe8&4\x10\xb6\xb2\xaf\xe0Q\xc8n\tm\x08\xbc\xc3\xfbP\xe7\xf9\xe4WNgc\x8e\x81m]x\xbbbB\xf2\xe1N\xa0br\x82`\xe4\x91\x8c\x1f"\xe4\xf2\x875\xa8(L\xedw\x1f\x05\xff\x94a\xa1T\x07\xee\xf7\xd7\xbd\xf3\x95\xd8\xf9\xb8\xd1au\xf6\x8c\xed\xd3\x8bo\xd0qa&m?\x922\xfbD\x0b)\xc5\xd6#&+\xf4\r\xf0q\x04*\xba\x0f\xd2i)\x88\xf7\xa5\x9ct5\xe7a]\x04\xd8\xac\xa1\xfcI\xb1x\xee\xfc\xc1\xe6B|9\xb6gO\xf4\xac\x9el%"\xeaq\xc4\xa3\xde\x95e\xfb\xba\xdd\x89,\xdaO\xea\xa9\x9a\x03<Dhg\xbc\x91\x07\x0eX\x00\xd9R'),
bytearray(b'\x16>*\xef\x9e"UK\xadb-\xe7\xca)\xda\xaf \xf3\n\xd9\xc9\xccN\x06\xe4\x1aQb\xd6\xccw\x0e\x1d\xfc\x05\x12x\xe1\xc9hxpa\x13\xbf<o\x8f\x9a#\xc9\xd8\x1d\x99\x07\xae\xcf\xd980Xy\x90\x17%w\xdc3\xf0\x18\x13\x10\xed\xc6p\xce\x0f)\xbf\xe3\xaf\x04c\xc0PO\x1d\x14\x9c\xf7\x9d\xfbzd\xfeF0\x10P\xf8P\x9c\xd3\xed\xf2\xdc\xab\xf4\xa7P\x7f\x00\xbby\x1e\xcc\x85\x86\x80\x8fH%\xc7a\x1e!9QH=dv\xee\xc0\xf2.\x05\x11\xf5\xba\xc6\xd5rL\x9eV\xd1\xfd\x93aQ\x0cF\x80\xea\xb1"\x1ah\x0b\xado\xf41\x90A\x04\x92\x11\xd0\xf6F\x7f\xd1r\xff\xc1\xe6\xc3\xbf\xce\xd3\x04\x86wx\xef\xb35\x12\x94NH\\\xe7\xc8\xbe,\xcd%\x91\xb7C\xbc\x16\x0e\x95\x8e\x0e\xeb\x02\xf4\x14\xe9\xd8Ic\xcf_tk\x9b\xba@[&AB\xe7}\x83\x14\x98\xde\x8a\x1e\xbf*x\xdc\x0bJ\x1e\x1b5\xca\xa3\x99\x13\x0e\xc6\x93\x1chw\xf7b\xcc:(\x95\xe88\x12p\xd3\xdd\xaf\x9a\x8d\xba\xb6\x88T\xc2lt\xf1\x8b\x03\xf4\x9d,\xcfe\x1a`\xc6\x94/?\xd15\xc9\xcc\xdb\xec\xeaQ\r[\xa1`\n\xd8\x9a\xb16\x8a\xf2\x1e\x0e\'\xaaa\xb0\xbd\xcfMl\xc6"g@\xa9\xe0\x07\xd8nr\xd1\x17\xfdw-\xcfl\xbe\x86\\\x80\xe7U\x8a\x12\xc3\xd9]\x85\xef78\xd7\xc2\xac\x8f\xc0"\x16\x9b\x0b\xf1\rLS^\xd1\x1dK\x85\xa1|\'\x15w\xa0\x1ff\xe3\xfd\xbe\x1b\xd0\t\x12\xf6\xb9j\x07\xeaV\xbb\xc1\xdc\xaf\x8a\xc7\x08-\xee\xc3\'g\xf9}@TM&<\x03\xbfh\xe1\x0f\x84@.\xd0\xa4n\x1a\xddQ\x8e=\x90}*\x8bK\xc1N\xe5\xbe*\xd06\x1f\x7f~\xcd\x88\x97\x91\xdeaf\xd3yp\xdd\xe0\xf5\xb0\x06\x1d\xbe\x08\xb8\xd0\xa1?\x1c \x12z\x93\xfb\x8c\xac\x8c1\xbd\xb1\x83o)\xe1\xb6\x9b\xaapShn\xb0Z\x9e\xb5tW\xa0\x8c\xbb\x10\xc94\x1e\xa4\xd9\xe9D\xff\xf9\x85\xbcB\xbc\xe2\xf9t\x86\xb5\xce\x1c\x1c\xc6\x98\xb3f\x8b\x16-F\xae\x82S\xd5HN\xfb\xaa$\xa9\x00\xe8\xb6\xefuiw\xc0(\xcd\x1dW\xaco\xc0\x053\xe2\xa4u\x9aP:\xb0x\xd7\x97\x93\x10\xce\xc0\xdf\x94l\x84\x89g\x85wGgjR\x8d\xaa\x89\xf7\xd7\xe9\xf6\xbe\x84\x8dwm\n\x8ft\x956($\xc7\x87\xa6\xe2\xaf|\xdd\x81H\x02Bz\xe9\x9a\xb8\x02\x00y\x8bXn+\xf5\x0f\xa5\xd2\xa8\xb7\xe3P\xcf\rpn\xef\xf1N"n\xee\xccV\x92\xf5\xbe\x8f\xc6\xd8\xe3x\xb9\xb1\x04\x0e\x9a\xa4Ga3\xb5\xdd\xf4\xb4@\xf7\xda*\x07\x86\x16\xb7\x18ki\xec\xd9>\xca\xc31\x15r$;\xf1\xaf\xa4\xd2u7\xdc\xc0\x9e\xb8\x8f\xd1My\x92r\xf5\xd1\\#\xfb_\x9b(\xab\xe3\x95\x9cEdk\xc1\xac\xe7\xfb+\xbdZ\xc5d\x85\xc0(V\xf6$\xd8w4[\xd8z\xae\tl\xf2\xa7\x07\x07o\t\xd0\x93\x95\xa6B\x99\r\xf4\xfcm\x1e\tgw\xf8\x90\xdd~\x12\x9c\xff\xe09T\xe5\ti\x15\xa9N\xf3\x19\xa2\xbf\xb4v\xc5~\xa9\xdct\xb6\xce\xf1\xdd6\xf4\x86\xffu\x0cR@\xbb\xa6\x17\xbcN\xb0\xd6)\xeba\x7f+\xdc\x9f\x0cU\x7f2\xa9\x07\xd0qo\xea\xc1I\x8b\x90\xbd\x13\x04\xa7\x13]"\xf7\x15\xae\xcf\xf0\xe3G\xdd\\O\x0cJ{\xf1G\xffK\xd9\xfdbc)\xa98\xa2\x88\x13MT\xaf\xda\xed\xc4\xa7\x19A\x9b\xe4L\x06\xfb\x05{\xa8\xa5l3\x8e;\x97\x1d\x18P\x13\x9cBn_@\xe3X\x18!\xb6\xc4\x99WQ\x9d\xd6)\x88\xb3\xcff%{\x83\xf2NK\xca\xf0\x02\x06\xb77\xdb\xd3\xbe\x7f\x98\x03\xbf<\xe3\x0b\x05\x8b7j6\x8e\x87\x86{\x83\x16\xb8\xceD\xdf3\x1d\x9d\xb7xG\xe5y\xa4\x82r\x81\xa0\xc8\xd6CP)\xbdV\xf6\x08\xd3l\xc1\x80q\xac\xd2\x92\x86p1cK[\xdd:\n`\xe2@\xfc\x13N\x80\x11\x98\xc9\xfb\xd8\xaaE\xeeeo\xca\xc5\x1a\x07\xa4}u9X\xef\xdb\xea\xb0/\x98!\xe0#N\xdc\xf8!\x8fu\x1a\xe9'),
bytearray(b'7\x1aE,\xd6ZA`l\xee\x05>j"x\xa3};&\x98\xda\x06O\xe5\xb0Z\xc2\x947*b\x89\xb6\xe0\x17\xc0\x96\x17\x8b\xd0\xbeb\xe42;Nh\x82\x83i\xb7\xc5\x88^\x14I\x10\x8a\xa9ez\xb2\xa6\xf8\x03\xc0%\xf3+x\xb6<\x02\xeb\x90\x8fkm\x9c\xd6\x99\xa9\xc0o\x94\xc1\x1eT\xe9\x84V\x89\xccMEkH\x05\xb3\x00G\x82\xdb\xd1>B\x07\xe7/B\xa0\t[\xb3\xdeC\xba\x94\xfb\x87=*IU\x11<Aw\x97?\xce\xa1\x07\x87\xf1\xed\x8c\xbf\x06\xbef-\xccl+ \xcf8\xec\xf52/\x00\xe3\xa8\x88\xbd\x9e\xf2\xb3\xc5\x83\xe5\xf6F\xb8\xd3S\n\xc7\xe2\xee\xfc\xf0g\xc5r\xbb\'_\xc3\xd8\xc2\xf6\xf0\x89JYt\x88\xb2O\xf6\x8f\x85\x90a\x9dQ0A\x996\x81)E\xdd&\xb3\x0c\x9a\xb2\x01\xef\xbbET\xbe{\x1a\x1f\xa8\xcbC\x14\x8d3\x805\x0c!dM\x89\xf2\xfb/\x07\x9b\xc9\xfa\xa6z\xcd\x12\x87\xdbo\xb3H\xa2b\x8d[\xdaD\n\n9\xf6\x02\x83pUj\x1b\xfc=o\xa4`\xdc\xd5\xa5\xe2\xd0\x11\x93V\xff\xa9\xed\xeb\xb8\xce\xde\xf4\xa6\x8d\xd9\xc1\xea\xc5\xd1\xaf#\x07\xe0V\xcb2\xbf;C?\xda\xbd\x1b\x97\x1dJ\x93\xb8\xde\x9d\x18\xeb\x9a\x86s\x05\x8f\xc1\x17\xd7\x05<>\xa9\x03\x1a\x04\x8e@\x8dN\x81\x06\xed\x02\x8f\x9b\x89?!\xa3[H\x072W_C?\x01\xae+\x92g\xb0\xc2DE:\x82D\x97\xf6\xb9\xd7H\xcc\xf5\xfe\naZ\xa4\xf8\x16CubL\n\x83\xe4<52\xd5\xa1I\tV\xdf\xf8\x07\xd2\xf6\xfe\\\x9d\xe0\xa1y\xda\xbb\x97\x9aE\xd2\xb7\x97\xe3\xa02\xd0kW\xd5=\xc8\xbd\x8a\xb5\xf9RA\xfb\xef\xd4\x19\xf3\x14\xcb\xdc&1\x82Kzc\x0e\x91\x86\xe7?\x1c\x91\x03\t8\x8d\xd4\x9b\x06\x14q\xa9l\x16\xa99\xf3\xe0\xc7\x9f\xd8\xa2\xfb\xd4+\xd3\x92\xcb\x10\xbd\x8b\x08\xb2.\xfd\xef6`\xba\x1eu\x80\xcd\xd4\x90\x9e\xcd=\x8b\x03\xa5;\x1c\xcd\xf5\x873<B\x90\xc3\x80\xa9\x1c"M=D\x8fP\x0e*\x98\x0e\xa8!v<\x8eO\xfb\xef\x11\xaf\xc1\xe0\x19fju\x96g:\x96\xbbv\x92\x0eA\xe1\x7fA+\xd3\xf6\x19M\xba\xf5\xb3\xec\x08\x91\xca\x19\xf6\x98TyI\xf3[f\xa7q\xc8\x9ev\xbdR\xf0\x85\xa6\xf5\xbc\x9c\x18\xbdH?~1#@\x9c\xf5\x00\xf6\xaf\xe7\xae\xab\xf4\xa3\xfe\xd2\x05\xea\xae\xec\'>m\xeeZ\x9f\xffk*\xc9\r\x86&?]4@7\xc2@\x04\x8e\xba\t\xbd\xe7eDC\xad~\xf8V1~\xee;\r\xa8\x05TSWOF\xec\xd2\x08\x129\x7f\xccR\x06\xd2\xb0\x11\xb1\t\x03\x84}I\x1b\xe8\xc2!\xc34e\xa0\xab\x88T\xcf\x14\x84\xa7J4x\x01\x97n\xeal\xfcL~V\xd6+\xd0\xc9U\xaf\x8a\xdb\x9e\x8e\x84\x9a\x98\xf2\xd1\x07vw0\xf2\xb5R\x94\xbfU\x87r!\xab\xce):!a{\x96#eMD\xb2\x0em\xc38\x0e\x8a\x9b\x89\x7f\xf1>\x01&\x10\xaa\x03\xdd\x91\x90\x04]\xd0\xdfES\xed%E\xb8\xfc\xed\xfc\x1e\xaa\x88\xd3\x10\xce\x93\t\xe0\xc1\x18%\xcc\x89]\x04\xc2~8I\xd2:\x03\x83\xe2\xed\xb9\x8c\xd1\xc0*\x882\xbb\xb0\xa1uP4\xcbR\x8f\x03\xac\x05\x9c\xbcl\x17\x9b\x9a\xbc\xa5\x0c\xbby\xb1\xba\x91\xa3\x86\xb3|_\xae\x10\xdf\xafyF}\x8d\xe1j\x9adi,K\x1d\xcby\x87\xcf\xc2\xcdp\xa4\xcbk\x8d(\'b\xcd^\xb9\xb1J6\x87\xf2\xf7j\xc4\xc6\'K\x1e\x0e\x91<@\x88\xec3\xf9\xeb\xc9\n\n\x13\x96\x8a\x16\xc7\xc4\xea(~\xb8K\xfe\x1awsvru\x94r\x97\xd9<\xca=dFT\xa9\x08\xbe\xf7\x1c\xc5\xe6]\xc1(R\x86\x1e\xf12<\x9aw\x99\x16\xab\xf2R\x1fD\xea\x95=\xbd;T\xb1\xa4\xa1\x02\xb1\xe9\xdb\xa2\xfc\xd4\xdd\xa7[\xac\xfc\xc4\x8b\xe4\xd3\x18\xdf\xae\xb4\x98eo.0\xa7M\xd0\xe9\xf8\x87D\x99\xcbJ\xfb\x03\x9fFiY\x16"\xde\xb3$\xd2\xbdJ\x98G\x8b\xb1_\xc9\xd7\x81\xca\xbbKK\xf7~R\xe4@\x99RY6'),
bytearray(b'0\x83\xcd\xbce\x92\xefu\x01?p\xff~\x10W\xa9\xfa\x16\x89\xd1\x9d\xbe\xf6:\xd2\x9c!B\xff$z\xb9\x97]\xdf\x02\xd5\xd2K\xd5\x0e\xc1}\x97,\x0f\xdb\xa5k\xb1\xc8 J@\xa2Y\x0e\xf1\xc1g\xbc}\x9f\x9b\t\x93\xb6\xab\xf7\xd2\xfaK\xe9(D\x8cq\xedf\x03\x0c\x07\x96\xa4D\xa2\xf8,\x8d\x87B\x96\x1fqY+\xd18\x8b\x94\x03<\x1d\x80\xbc#\xcc\x8b\xef\xfdGo\xac(h,\x11\xa3\xa0\x91Y\x91!\xdf\xd2\xfe9\xf3\xa0x\xd5\xec5Qf\xd1\xdabK\xf5\xbe\x9244\xb0\xa5>\x06(@\xc6\x11Z\xe7n%,\xff\xb4\xe9\x97 \x894&\x08n~\x17\x87\xb2\x8c\x15\xcc\xa4\x8e\x86\xfb\x0f\x8c\xa4\xa6 \x9d\x92\x95Zr<\t\xdeC6\xf9\x0c\xa9mq\xcd CB\x9c\xa6g\xb7\xad \x8dY\xcb\xb5\xb7\xd4&\x9cq\x85\r\xc1\xdd\x1f\x89 \xf2\x9e\xee{\xa3h.\xba\x0e2\xb3\x84\x93\xacFY\xf5{ZG\xa4\x9a\x7fT\x81\x04\xcd_\xd9\xc75\xfbOR4\x0br\xd3\x11\xce\x90\x97-]\x87^Ug1\x14\x02\x86IS \xbd2\x128\xf1\xd0=\xa8s\x16/8:.O{\xa8\xf4ZW^\xda\x89\xa7\xa2\x11\xf8/\xa9\xd0\xf9\x19\xf67_\x9bkA\xdf\xbc\xe1v\xf6\x9e\xe6z\xe0\xd4\xb4\xef\xffuz\x14\x0c\xcd\xcb~8;Y^\xee\x06\xec\'c\xe3#\xa3\x10\xb9\xa9P\xd8\x08\xb15\xdc\xe4\xfb\xed0n\xfd~3\xd5Sh\xf3\x94\x97\x8bb]\xcb\xa3f\x0b6\xc3\x1a{\xda\xc9\xb1M\xa1\xf7\xa1\x96\xec\x11\x07\xab\x81\xd9-\xc9zz\xec\xf3W\x95^APs\x94q\t;\xb4\xea\xf4D\xeaA$\xf8+\xf9.\xbf8.^u\xfa\xbb\x17\xc7\xeb_u\x00\xa4{\xe4$\xc21XJ5\xf1\xbe"bd\x87J\x8d\xf9\x8e\x00\xa3\xb4\xdb3\x99L\x95\x1f?G\x86\xa9HL\xb8\x039+\xdfe\x0c\xeb\xa6u\xecU\xa6\x99\xb0A\x1a^\xf4Od\x9fw\x02h\xc28\xbbwp\\hi\x00\xd7\xeb\x8cKv\xe4\x9c\x1a:\xb1n\x84:\x0f\x81[\x9a\xed\xdc\xac^\xf1\xba.\xc3=3\x1c\xfaM\xdbr\xf50\xe9\xb9e\xf0-\x99+\xbe\xaa|yb\xb5\xf1S\xb8\xb5\x08\xd8\xa9\xfc\x84\xad\xbc\xd3\x15e\xb3R\x91cA\xef\xddO\xf8\xc2\xc4\xb8\xbce\xdb\xb9m\xe8\xdb\x15\xf2\xf6\x9d\x16\x1c|\xa62K\x9b\xa1\xf3\xba\x908\xfc,,\xa5\x9e\xba\x0b;C\xe7\xe9s:\xd1\x0f\xe6Y\xa9\x8b\\\xf1\xdd\x83\x03\x04\x9e$\x8a;\xa5*4\xab\xaaGV\x13R\x8f\xfa\x19\x18\xcf\xd6Ibe\x19\xa8\xcc\xf0\xbcv\x16\x93\xff\\\x13\x1d\x89\xff7\xeb\xf4\x81\xc2\xd3\x9aN8pCp\xa1\xd5%\x8c\x19\x94\xee\xa7\xc1\xf5\xf5\xff\xb5O\xfb\x04\xfd\x02A\xe8\xfaz|\xbd\x9e\xdb\xd5\xe0bO\x8eW\xfa\xe8\xc1\xac+N\x95\xc0\xa5\xcaN\x1cw\xa2bgfu\x0e\xa6\xac-2N\x9c\x08&\xc6\xdf1\xec<\x9c\xabm\xe5\xac\xa2z\xfa|\x8a#e\'{]\x92v"\xf9\x0f\xe6S\x95\x19\xbc\xacAC\xbd\xf7\xcb!(\xf9\xc2~\x1c\x95\r\xd6\x8a\xf8\xede\x1ev:\xda\xbf\x80\x1fSi\x81,sr:6\xb3K8\xd8\x86\xf6\xc2yl\xa0K$.\x0f\xca#\xc9vv9D%\x8c\xe3qJ\x94|\x01\x88\xf6\x1f1\x0e\xd3\xd0\xb1?l\x14uy\xa8\xa4OH\xcb,\x04x\xf4\xef-FU\xa9i\xc2\x8d)\xed\xe0\xa7\xdaX%\xb2\xb2\xe6^\xe3\xa6\xcd\xaahX\xef|.^\x15>\xd1\x0b\x1c\x85\x18\xb4\x1c\xdaAy\xccP\xc6\x17\x85\x19A\xaf\x11N\xbf\xb9\xbe%]\xe0@kP.\xb6i,Z\xb6\x9f\x8cH2\x8a\xd8\xa8FQPl\x14m$t\xc1\xe5u\xb3\xf9\xab\xcbsa\xd7\xf2\x82&\x16\xfa\x13Wu\xd3+\x87\xe8|n>+\xb81\x03^\xc7\t\xa4[\x90O\xebT%\x82\xd5\x05O\x9f\x87\xddF\x82t\xbf\x04\x8b}s}\xda\xc8|\xae\xb1\x9a\xad\xbc\xce\xef\xa2t\xd2\x87\x8a\xaa\r\xe9yoL\xaa,\nj\x04n\t*\x93A"8)g\xcf\n\xba\xf2Y'),
bytearray(b'\x8f\xafe\xf6YA\xe2\r\xfdB\xf8b\x8a\xdf\xb6\xd2\xbet}=u\x13\xe3f\xdb(\xd3r\xd9K\x88d\xdcS\xfe\xf6d\xa2\x95\xee\xad0\xcd\x08\xba\xfc\xf9\x10\x11jB\x1d\xfb\xbd\xe9C\xa7\x82\x86\x16[\x81\xac\xff\x1a\xe4>\x84\xe1}\xba\xa8_Q\xb4\\\x0cJ\xdc\xda6`\xa9\x86)7\xd8\xfa\xd4\x890\\\x07\xf6s:\xf5c\xb2\xd1\xcfW`\xa6\x96=\xe8P\x18\xb8\xa3^\xa5\xb0L\xea\xb1\'i\x12%;\xbb\xc8A\xca\x9c&\xfc\x08\xab\xd5\x89\xfe\xbb\x05\xfa\x00\xebkU\xb6g\xf8qt8\x10.\xd0\xbe;\xe6\xb2L\x95M,\x9c\xb0\x06\x98\xb4\xae\xa1>\x058|;\xd3\xc3\x93\xc1\x80YH\xe3i\xd4\xd0\xd35h\xc0)4\x0fv\xc5\xd1\x96 R\xe3\xbc\x9e&(\xd9l\xf2\xdb\xfe\x12\xfa\xbb\x8a\x0es\xc2IK\xa1\xca\x94`\xe3\x14X\xfb\x84K\xb9\xc2Ch\x86#\x95\xbf\xd0\xa7\x10n\x94\xcd\x07\xc7\xdc\xd0\xc0\xcdL\xb4AL\x8d\xae\xb3\x7f\x95{X\x929\x91N\xf6\xe3\xa5\x1d;\x8d\xfd\xd8\xee\x07\x90\xd2\x13\xafG\xc9\xc3\xfe\xb3\x99\xd6(\xbax\x94\x11\xe5\xa6\xc9\x04\xa6~7XcqZ\xe8{\xad0\xc7\'\x9f\x1bZ\x95\xbf}\x1es\xa6\x1c\x1d\xff\xce\x05\xaa\x95G\t\xf7\xd0\xb1\xe4\x05 \x1b\xac\xac\x0c\xfa\xc1\'\x16/\x94\x85\xc0\x82\x88\xba$1\xcd\x88\x9cu\x88\x961\xa0\xdd\xa3\xe0v0\xdbD\xf4{\xbd\x13\x03\xb3\x7f\x03\xd7?\xc2\x00K\x07i\xff\x12\xfe\xdc\x86\x1eF\x80\\2\x84kT\x7f%\x13\x07:\xf8\xcd>(`y\x9aI\xa2\xd2\x1f\x8e\xcfy\xaa\xa7"\xd7\xef\xfap*{Yn^\xd9u\x03\x08\x85\x9e\x99\x85\xb5\xfa\xd3"\x9cOUH\t\x02\xd5\x1d\\;\xc7\xb9\xd4\x86V\xfa\xab\xb9\xbbU\x89\xf1\xf6Z\xa1*-\x10&\xcb|rqy$\xe6P8\x9e\xe2G"&\xa2\xfd\x18\xf4\xd0J&\xb6\xa0\xf0}Q\x96\xe4\xb1\xc8"\xb0x\xeb\xd98\xb4&\xf6\x05;\x92]\xa7\\\xe5j\x04D\xf6c\xbe\xd6\x8br3\xae\xb4V\xfd\x9d\x0f\xc0\xa2G\x1e\x0c{\xe7!O,\xcc\x8d-U\xd5:F\xca\xe2\xccyA\xf1\x0b\xd6\xae\x8ar\x9c,\xc59\x02\x8e\x90\x8f\xddI\x9c\xe0H\x9f\xd3>\xed\x9d\x82lU\x81\xd6\xf7^7(\xd4"k\xc4\x1cO\xa8e\xa7\xce\x1d0\xa0\xf0\xb0\x02\xb3i\xbb\xe1\xe1Z\x8e$(\xf9mM\xb6$\x0e\x01+\'\x10F<dF\xa9\xde-\r\x97&\xcfx\x9e\xb5\x10R!\xc5\xe5\xcdG\xde\xaa)\xde\x9f\xdb f\xc5;\x98Wyeg\x1fkV\xff\xaa\xbd\x129\x03\xd8\xa3\x80\xe6\xb5c_\x9d\xc8\xac\xba\x94\xbe,&@\x9ak\xf5c\x1b\xfc\x8bH\x83\xf9\xfb\rY\xc9\xa6\xfe5\x91\x92l\xcb`\xba\xad\xcat\xb3T\x99Y\t\x9f\xe1\x87\xdes\x81\xe0\xfbX\xeeGC\xd7\x06\xa4q\xd7=\xf6\x85H\x1d^\xd6\xa2\x010\xaf\xb3\x89\xb6zS\xca\xe2$\x86\xe6y\xba\xc4%\x87%L\xbf\xc5\xa2\xb4N\x85\x1cF\xe4\x06\xb2\xef\xccT\xe9\xc0\xa2I\x9e\x97\xb28\x82\x14\xa7\xe5q\x02\x1f\xba\xabyf\x07\x8c\t\xce\\\xcfR\xd5\xfd\xfc\xd9\xce\xa0\xfd\xd5\xab\xa2\x99XZ\xa7?O\xdfIl\x8b\xfdU\x8a\xbf]\xe8\x03y<\xc5\xd5\x0b\x04r\xaeO\xa9\x06}\x82f\xf0&\x93\xab\xb3\xe3@\xc60o\x1e(\xe0\x0e\xff\xfc.\x87\x0f?\xdcT#\x96\x11\xf2\xe7\xb0\xd6o\x83\xe02\x91\xa56}\xbf\x8a\xfd\xb4\x01\x84\x04;%\x94f\x9bu\xadR\x17\xe19\x0c\x1e#U\x8b\xd9c\xde\xbc\xb4\xba\x11\xfa#\xf5\x1d\xcb\x16\x0f\xad\xf7#\x8cDUz\x88\xf7\x1e\'W\xd7\xd0Qu*\x8a^\xe9Z\x97J\x1b}A\x9c\x10D\x92\x03\xa7\xf4g\x1d\xad\xa4\x8aG\x837\x81\x07\xa3J}\xc5\xfe8I\xf7\xe0g\xe1@\x9a\x1c\xa7]\xa8l{\xc8\t\x97\xb2\xa9\xb7\xa8\xe1D\x14\x89\x85\xb3\xd8j\x1e`\xa8:\xd1\x98\x85m\x85\xab%{\x8c\xb7\x1d\xa0[\xc6\xe1\xbc5\xf6\xae\x12\xb9L.\xd5\xd2\xa2S\xadhz\x05!g[\xd8F\xec\x96f\xb2\xfa#'),
bytearray(b'\x99\x96\xec[F\xb8$\x0c\xb7\xfe\xe08\x97\x8b\x91x\xbf\xbf&a[fr\x01\xc7L\xeb\x08\x95\xf6\x19\xe9\xf8\xd2\xc6\x16`\xd5\x08cy[\xe9\xc9\x8bA\x03\xd1\xef\x83>^\x90\xab\xb3:\x84\xfa"\xed\xc1A?D<\x17c\xba\xba`\xfd\xaa\xae\x9d\xc7:j\xb3#\xa0\xa6"R(P\\\xdc\xc9s\x00&2\x10\xe7*jvJ\xa0\rU\x80R\xe1\x8e\xe9\x97\xe6\x1a\x05W\xc1{\xe3y\xbf.\xbb\xde\x11j\xf4\xdfCc\xaa\x15@2\xe6V+w:\xebD&\x9a\x8cAC\xfa7\xd6<!|\xd5\xae\xe6\xca\xe7\xbbc\x15yB7\xb7\x15\x9b\xca\xc1Rw\x9e\x13\xfa\xa4\x1f\x8a\x15VB\xe4<=\x92H"\xbcv\x18]\x83\xd7\xa6\t\xa0L\xf7}\x13\t:y\xdfZ\xc4\x19\x07\t\xa3\xc6\xeb\xa5`\x94\xbf\xb3\xe1\x95\xe0xh\xa4(um\n\x0cB\x19-\x83zU\xcd\xd8{\xb4\xab\x08Sl\xed\x0eU\xeb\xe1\xd4|\xc7\\*""\xbaB\x88\x83\xe0\xe8V\xf4{z&\xd7\xf4\x81>\xb2\xd1\x8f\xf4\xb4d\xe4\xbb~5\xbaU\xa5\x8f\xa09\xea\xc1C\xb5\x9fR\x05E%\xe0\x0bp\x1b\x88\xbb\xf1lI\xd5\xe0{\xa4\x0e\x12)o\x8a\xefr\x11\xe2\xde\xca\x14\xb0\x8c\xf1\x97\x1b\xb2o\xe8g\xc5M\xcd\xb3I\xa7C\xd7\xd7\x0b\xa5c%\xf9s\xf2I\xba\x82\x8et(W\xf8\xf1\x8a\xd6\xcc\xbc\x13\x08O\x8a\xe6\xfa`?\xae\xc2H\x1aAN)\xe8\xb5\xc3\n\xd6l~\x8d\xd73\x18r&\x98\xfb7\x84\xbc\xff\xd8\xdd\xac%u,\xfb\xbf\x04\xcc\x9d\xa4a\x17\x90\x83\xd3\xe3UV\xcd\x9f\n"6_8R\xaf`NH\xf2\xf93,_E\xa99\x99\xbbL"\x9b\xd1qv91\x844\xea\x9f\xe7\x8a&\xd0v\x0c\x8c\xd8\xfd\xb8\x97\xff\x98O\xc8\xc9:\xdf\x89\xc2\x82\xac\xcaw\xa4\xf8h\xa8i\xa5A\x07s\xbb\xed\xcdLV{\x8b\xf9\x81\xcb2\xad\xb7>\x9a\x97\xcb\xe8|x\xa3p\x93\'\xff\xce\x91\x85\xb8\xdaq\xac\xcd\x90n\x1b\xffr\xd2\xdd\xf2<\xc1\xe5\x94\xc6\xbe0\xe61\xbe\xa4\xe4\xb2\x93\xa9\xaaoI\x11\x04\xc8\'3\xe0FJ\x9f^N\xed\x8f\xb4\xcb\x128\xa3x\xeb\xc8\xdaI\xeaBBb\x1d\xc2\xe3\xdc\x7f%\xc6J\xa3\x1fF\x12\xa3s\xc8C\xe2\xde\xc5\xe0\xe7\xeb\\\xf8\x99\xf7\xd6\xa4vPg\x8f,\xe0\xcf\xef8d\x9cZ\x80\xc1\xac\xba\x14s\x8d\x05\x8a.\xd5D\xe3\xcd\x1e\xf7\xd99\xb8\xb4\x81\xf5\xa1\x1a\x11m6\x80\'y\xef\xc3r~E3h\n7Q\x14V#\xde\x81\xcc\xe0V\xd7\xb3\x84\xd5\x9c\x83\x91\xaf\xf2\x832\x10\xf1\\\x05\x14L\xdf`\xf3\xf5\x0e\xd98N6%[.2\xc3\xd4L<\xc9(\x96\x88\x84\xca\xfb\xf7e\xe4\xdd\x9e\x91DK!\x8a\\\x85\xc1U\x91\xc8APvy\xf6z\x1b\xfc\xa1\x02^;\xb8\xa5\xe0\xab\xb8\xee.\xac\t\xb9\x16T\xb7g\x11/\x0c\x81\x93\xdeR\x07\xaf\x85\xd1\xbc\xb5\xe0\xc9\xcdL\x9c\x9d\xfc\xbf\xc3K+\x87\x07u\xe6\x02\xd4c\xf4\x857=\x14\xc3{\n\xc7\x08\xcd\xee2\xa9\x9f|\x04tu\x1d\n\xd4\xdbx>A\xc0D\xf8\xadOF@\xad\xa1\xb1i\x12:1\xd9\x05N}\x13\xd6C8\xc0\x83z\xb5J\xee9\xcf\xa3\xc5\xd2\xcb\xd2\x1f\x96\x8b7\x0fw\x9b"\xab\xb9\xa2z\x81\xa8M_\xe3\xa2z\xb8\xc2\x10Q\x05,\x1c\x81\x03,\xc7di\xba\x85\x07z8!F\x989\xcd\x17$\'\xc0^\t\xd6\xe8g\xabg&\xdc\x8f\xa1V\xd1\xb0\xbc\xcf\xe5\x82_\xdf\xe5\x8d\xa8\xe4\x91\x9a\x8eC\x92\xe0/\x93E\x7f\x00\xfb\x88\xaf\x87q\xb9\x7f\xf0\x17M\xb4R\x86\x19<\xd3\x9e;\xca\xa0|\xec\x1f\xbf\x92\xb9\x83[\x19eZ\xc1\x1f\x05\x01\xc2\xbe\xca!\xc3\xaa\x92\xcfE+,\x03\x1c\x1fAA\x04v\xc9\xbc\xe6!\xf9\xfe\x82Y\xef\xb55\x99v\x82\x16\x15\xd1\x9d\xf6t\xee\xd5\xc1\xa5<Z\xd5\xd4\xd1\xdf9\xa4%\xcd\xcd\xd6\xbc\xf7\x1e\x1e"\xb6\xeef\xf0*yk\x7f\xc9>\x8f.\xa9\x01cl\x87\x95<&bW\xbf\x92\x88\x1e0s[')]
}
def _ff_key_share(name):
if name in _ff_precomputed:
return random.choice(_ff_precomputed[name])
ff_map = {'ffdhe8192' : goodGroupParameters[6],
'ffdhe6144' : goodGroupParameters[5],
'ffdhe4096' : goodGroupParameters[4],
'ffdhe3072' : goodGroupParameters[3],
'ffdhe2048' : goodGroupParameters[2]}
generator = ff_map[name]
secret = random.randint(2, generator[1])
ret = numberToByteArray(powMod(generator[0], secret, generator[1]))
return ret
class Xmas_tree(HelloConfig):
"""
Create a Xmas tree (all options enabled) Client Hello message
Creates a ClientHello message with maximum number of options enabled,
currently for TLS 1.3 protocol.
"""
def __init__(self):
super(Xmas_tree, self).__init__()
self._name = "Xmas tree"
self.version = (3, 4)
self.record_version = (3, 4)
self.ciphers = []
self.ciphers.extend(CipherSuite.ecdheEcdsaSuites)
self.ciphers.extend(CipherSuite.ecdheCertSuites)
self.ciphers.extend(CipherSuite.dheCertSuites)
self.ciphers.extend(range(0x0100, 0x0200))
self.ciphers.extend(CipherSuite.dheDssSuites)
self.ciphers.extend(CipherSuite.certSuites)
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(TLSExtension(extType=ExtensionType.renegotiation_info)
.create(bytearray(1)))
groups =[GroupName.secp256r1,
GroupName.secp384r1,
GroupName.secp521r1,
GroupName.ecdh_x25519,
GroupName.ecdh_x448]
groups.extend(GroupName.allFF)
ext.append(SupportedGroupsExtension().create(groups))
formats = [ECPointFormat.uncompressed,
ECPointFormat.ansiX962_compressed_prime,
ECPointFormat.ansiX962_compressed_char2]
ext.append(ECPointFormatsExtension().create(formats))
ext.append(TLSExtension(extType=ExtensionType.session_ticket))
ext.append(TLSExtension(extType=ExtensionType.max_fragment_legth)
.create(bytearray(b'\x04')))
ext.append(NPNExtension())
ext.append(TLSExtension(extType=ExtensionType.alpn)
.create(bytearray(b'\x00\x15' +
b'\x02' + b'h2' +
b'\x08' + b'spdy/3.1' +
b'\x08' + b'http/1.1')))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
ext.append(TLSExtension(extType=ExtensionType.status_request_v2)
.create(bytearray(b'\x00\x07' + # overall length
b'\x02' + # status type
b'\x00\x04' + # request field length
b'\x00\x00' + # responder id list
b'\x00\x00'))) # request extensions
sig_algs = []
# some not yet standardised algorithms:
for s_alg in [4, 5, 6, 7]:
for alg in ['sha256', 'sha384', 'sha512']:
sig_algs.append((getattr(HashAlgorithm, alg),
s_alg))
# some not yet standardised hashes:
for s_alg in [SignatureAlgorithm.rsa, SignatureAlgorithm.ecdsa]:
for alg in [7, 8, 9, 10, 11]:
sig_algs.append((alg, s_alg))
for alg in ['sha256', 'sha384', 'sha512', 'sha224', 'sha1', 'md5']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.rsa))
for alg in ['sha256', 'sha384', 'sha512', 'sha224', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.ecdsa))
for alg in ['sha256', 'sha384', 'sha512', 'sha224', 'sha1', 'md5']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension()
.create(sig_algs))
ext.append(KeyShareExtension()
.create([(GroupName.secp384r1, _ec_key_share('secp384r1')),
(GroupName.secp256r1, _ec_key_share('secp256r1')),
(GroupName.secp521r1, _ec_key_share('secp521r1')),
(GroupName.ffdhe8192, _ff_key_share('ffdhe8192'))]))
ext.append(TLSExtension(extType=ExtensionType.heartbeat)
.create(bytearray(b'\x01'))) # peer allowed to send
ext.append(PaddingExtension().create(512))
ext.append(TLSExtension(extType=ExtensionType.encrypt_then_mac))
# place an empty extension to trigger intolerancies in specific servers
ext.append(TLSExtension(extType=ExtensionType.extended_master_secret))
# interesting ones are 0, 1
self.compression_methods = list(range(0, 80))
class HugeCipherList(HelloConfig):
"""Client Hello with list of ciphers that doesn't fit a single record"""
def __init__(self):
super(HugeCipherList, self).__init__()
self._name = "Huge Cipher List"
self.record_version = (3, 1)
self.version = (3, 3)
self.ciphers = []
self.ciphers.extend(CipherSuite.ecdheEcdsaSuites)
self.ciphers.extend(CipherSuite.ecdheCertSuites)
self.ciphers.extend(CipherSuite.dheCertSuites)
self.ciphers.extend(CipherSuite.dheDssSuites)
self.ciphers.extend(CipherSuite.certSuites)
self.ciphers.extend(range(0x2000, 0x2000+8192))
class VeryCompatible(HelloConfig):
"""
Cipher compatible client hello with minimal intolerancies
Create a Client Hello that can connect to as many servers as possible
without triggering intolerancies (with the exception of TLS extension
intolerance)
"""
def __init__(self):
super(VeryCompatible, self).__init__()
self._name = "Very Compatible"
self.version = (3, 3)
self.record_version = (3, 1)
self.ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_RSA_WITH_RC4_128_SHA,
CipherSuite.TLS_RSA_WITH_RC4_128_MD5,
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(SupportedGroupsExtension().create([GroupName.secp256r1,
GroupName.secp384r1,
GroupName.secp521r1]))
ext.append(ECPointFormatsExtension().create([ECPointFormat.uncompressed]))
ext.append(TLSExtension(extType=ExtensionType.session_ticket))
ext.append(NPNExtension())
ext.append(TLSExtension(extType=ExtensionType.alpn)
.create(bytearray(b'\x00\x15' +
b'\x02' + b'h2' +
b'\x08' + b'spdy/3.1' +
b'\x08' + b'http/1.1')))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
sig_algs = []
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.rsa))
for alg in ['sha256', 'sha384', 'sha512', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.ecdsa))
for alg in ['sha256', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, alg),
SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension()
.create(sig_algs))
class IE_6(HelloConfig):
"""Create a Internet Explorer 6-like Client Hello message"""
def __init__(self):
super(IE_6, self).__init__()
self._name = "IE 6"
self.version = (3, 0)
self.record_version = (0, 2)
self.ciphers = []
self.ciphers.extend([CipherSuite.TLS_RSA_WITH_RC4_128_MD5,
CipherSuite.TLS_RSA_WITH_RC4_128_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.SSL_CK_RC4_128_WITH_MD5,
CipherSuite.SSL_CK_DES_192_EDE3_CBC_WITH_MD5,
CipherSuite.SSL_CK_RC2_128_CBC_WITH_MD5,
CipherSuite.TLS_RSA_WITH_DES_CBC_SHA,
CipherSuite.SSL_CK_DES_64_CBC_WITH_MD5,
CipherSuite.TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
CipherSuite.TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5,
CipherSuite.TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
CipherSuite.SSL_CK_RC4_128_EXPORT40_WITH_MD5,
CipherSuite.SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5,
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA,
CipherSuite.TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA])
self.ssl2=True
class IE_8_Win_XP(HelloConfig):
"""Create a Internet Explorer 8 on WinXP-like Client Hello message"""
def __init__(self):
super(IE_8_Win_XP, self).__init__()
self._name = "IE 8 on Win XP"
self.version = (3, 1)
self.record_version = (3, 0)
self.ciphers = []
self.ciphers.extend([CipherSuite.TLS_RSA_WITH_RC4_128_MD5,
CipherSuite.TLS_RSA_WITH_RC4_128_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_RSA_WITH_DES_CBC_SHA,
CipherSuite.TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
CipherSuite.TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5,
CipherSuite.TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA,
CipherSuite.TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA])
class IE_11_Win_7(HelloConfig):
"""Create an Internet Explorer 11 on Win7-like Client Hello message"""
def __init__(self):
super(IE_11_Win_7, self).__init__()
self._name = "IE 11 on Win 7"
self.version = (3, 3)
self.record_version = (3, 1)
self.ciphers = []
self.ciphers.extend([CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_RC4_128_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_RSA_WITH_RC4_128_MD5])
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(TLSExtension(extType=ExtensionType.renegotiation_info)
.create(bytearray(1)))
groups = [GroupName.secp256r1,
GroupName.secp384r1]
ext.append(SupportedGroupsExtension().create(groups))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
sig_algs = []
for s_alg in ['rsa', 'ecdsa']:
for h_alg in ['sha256', 'sha384', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, h_alg),
getattr(SignatureAlgorithm, s_alg)))
sig_algs.append((HashAlgorithm.sha1, SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension().create(sig_algs))
class IE_11_Win_8_1(HelloConfig):
"""Create an Internet Explorer 11 on Win8.1-like Client Hello message"""
def __init__(self):
super(IE_11_Win_8_1, self).__init__()
self._name = "IE 11 on Win 8.1"
self.version = (3, 3)
self.record_version = (3, 1)
self.ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA]
ext = self.extensions = []
ext.append(SNIExtension())
ext.append(TLSExtension(extType=ExtensionType.renegotiation_info)
.create(bytearray(1)))
groups = [GroupName.secp256r1,
GroupName.secp384r1]
ext.append(SupportedGroupsExtension().create(groups))
ext.append(TLSExtension(extType=ExtensionType.session_ticket))
ext.append(TLSExtension(extType=ExtensionType.status_request)
.create(bytearray(b'\x01' +
b'\x00\x00' +
b'\x00\x00')))
sig_algs = []
for s_alg in ['rsa', 'ecdsa']:
for h_alg in ['sha256', 'sha384', 'sha1']:
sig_algs.append((getattr(HashAlgorithm, h_alg),
getattr(SignatureAlgorithm, s_alg)))
sig_algs.append((HashAlgorithm.sha1, SignatureAlgorithm.dsa))
ext.append(SignatureAlgorithmsExtension().create(sig_algs))
ext.append(NPNExtension())
ext.append(TLSExtension(extType=ExtensionType.alpn)
.create(bytearray(b'\x00\x10' +
b'\x06' + b'spdy/3' +
b'\x08' + b'http/1.1')))

165
cscan/constants.py Normal file
View File

@ -0,0 +1,165 @@
# Copyright 2016(c) Hubert Kario
# This work is released under the Mozilla Public License Version 2.0
"""Extend the tlslite-ng constants with values it does not support."""
import tlslite.constants
from tlslite.constants import CipherSuite
CipherSuite.ecdheEcdsaSuites = []
# RFC 5289
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C
CipherSuite.ietfNames[0xC02C] = 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B
CipherSuite.ietfNames[0xC02B] = 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024
CipherSuite.ietfNames[0xC024] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023
CipherSuite.ietfNames[0xC023] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
# RFC 4492
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A
CipherSuite.ietfNames[0xC00A] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009
CipherSuite.ietfNames[0xC009] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
# RFC 7251
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0Ad
CipherSuite.ietfNames[0xC0AD] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CCM'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_256_CCM)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC
CipherSuite.ietfNames[0xC0AC] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CCM'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_128_CCM)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF
CipherSuite.ietfNames[0xC0AF] = 'TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8)
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE
CipherSuite.ietfNames[0xC0AE] = 'TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8'
CipherSuite.ecdheEcdsaSuites.append(CipherSuite.
TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8)
CipherSuite.ecdhAllSuites.extend(CipherSuite.ecdheEcdsaSuites)
CipherSuite.certAllSuites.extend(CipherSuite.ecdheEcdsaSuites)
# obsolete stuff
CipherSuite.TLS_RSA_WITH_DES_CBC_SHA = 0x0009
CipherSuite.ietfNames[0x0009] = 'TLS_RSA_WITH_DES_CBC_SHA'
CipherSuite.TLS_RSA_EXPORT1024_WITH_RC4_56_SHA = 0x0064
CipherSuite.ietfNames[0x0064] = 'TLS_RSA_EXPORT1024_WITH_RC4_56_SHA'
CipherSuite.TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA = 0x0062
CipherSuite.ietfNames[0x0062] = 'TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA'
CipherSuite.TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003
CipherSuite.ietfNames[0x0003] = 'TLS_RSA_EXPORT_WITH_RC4_40_MD5'
CipherSuite.TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006
CipherSuite.ietfNames[0x0006] = 'TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5'
# DSS
CipherSuite.dheDssSuites = []
CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013
CipherSuite.ietfNames[0x0013] = 'TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA)
CipherSuite.TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012
CipherSuite.ietfNames[0x0012] = 'TLS_DHE_DSS_WITH_DES_CBC_SHA'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_DES_CBC_SHA)
CipherSuite.TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA = 0x0063
CipherSuite.ietfNames[0x0063] = 'TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA)
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032
CipherSuite.ietfNames[0x0032] = 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_AES_128_CBC_SHA)
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038
CipherSuite.ietfNames[0x0038] = 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_AES_256_CBC_SHA)
CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040
CipherSuite.ietfNames[0x0040] = 'TLS_DHE_DSS_WITH_AES_128_CBC_SHA256'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256)
CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006a
CipherSuite.ietfNames[0x006a] = 'TLS_DHE_DSS_WITH_AES_256_CBC_SHA256'
CipherSuite.dheDssSuites.append(CipherSuite.
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256)
class ExtensionType(tlslite.constants.ExtensionType):
"""Definitions of TLS extension IDs."""
status_request = 5
alpn = 16
session_ticket = 35
heartbeat = 15 # RFC 6520
status_request_v2 = 17 # RFC 6961
padding = 21 # RFC 7685
max_fragment_legth = 1 # RFC 6066
# From: Eric Rescorla <ekr at rtfm.com>
# Date: Mon, 7 Dec 2015 05:36:22 -0800
# [TLS] TLS 1.3 ServerConfiguration
early_data = 40
pre_shared_key = 41
key_share = 42
cookie = 43
class GroupName(tlslite.constants.GroupName):
"""ECDH and FFDH key exchange group names."""
allEC = list(tlslite.constants.GroupName.allEC)
allFF = list(tlslite.constants.GroupName.allFF)
ecdh_x25519 = 29
allEC.append(ecdh_x25519)
ecdh_x448 = 30
allEC.append(ecdh_x448)
eddsa_ed25519 = 31
allEC.append(eddsa_ed25519)
eddsa_ed448 = 32
allEC.append(eddsa_ed448)
all = allEC + allFF
class HandshakeType(tlslite.constants.HandshakeType):
"""Type of messages in Handshake protocol."""
certificate_status = 22
session_ticket = 4

200
cscan/extensions.py Normal file
View File

@ -0,0 +1,200 @@
# Copyright 2016(c) Hubert Kario
# This work is released under the Mozilla Public License Version 2.0
"""Extra TLS extensions."""
import binascii
import tlslite.extensions
from tlslite.utils.codec import Writer
from tlslite.utils.compat import b2a_hex
from .constants import ExtensionType, GroupName
from . import messages
# make TLSExtensions hashable (__eq__ is already defined in base class)
tlslite.extensions.TLSExtension.__hash__ = lambda self: hash(self.extType) ^ \
hash(bytes(self.extData))
class RenegotiationExtension(tlslite.extensions.TLSExtension):
"""Secure Renegotiation extension RFC 5746."""
def __init__(self):
"""Initialize secure renegotiation extension."""
super(RenegotiationExtension, self).__init__(
extType=ExtensionType.renegotiation_info)
self.renegotiated_connection = None
def create(self, data):
"""Set the value of the Finished message."""
self.renegotiated_connection = data
@property
def extData(self):
"""Serialise the extension."""
if self.renegotiated_connection is None:
return bytearray(0)
writer = Writer()
writer.addVarSeq(self.renegotiated_connection, 1, 1)
return writer.bytes
def parse(self, parser):
"""Deserialise the extension from binary data."""
if parser.getRemainingLength() == 0:
self.renegotiated_connection = None
return
self.renegotiated_connection = parser.getVarBytes(1)
return self
def __repr__(self):
"""Human readable representation of extension."""
return "RenegotiationExtension(renegotiated_connection={0!r})"\
.format(self.renegotiated_connection)
def __format__(self, formatstr):
"""Formatted representation of extension."""
data = messages.format_bytearray(self.renegotiated_connection,
formatstr)
return "RenegotiationExtension(renegotiated_connection={0})"\
.format(data)
tlslite.extensions.TLSExtension._universalExtensions[
ExtensionType.renegotiation_info] = RenegotiationExtension
class SessionTicketExtension(tlslite.extensions.TLSExtension):
"""Session Ticket extension (a.k.a. OCSP staple)."""
def __init__(self):
"""Create Session Ticket extension."""
super(SessionTicketExtension, self).__init__(
extType=ExtensionType.session_ticket)
self.data = bytearray(0)
def parse(self, parser):
"""Deserialise the extension from binary data."""
self.data = parser.bytes
return self
def __format__(self, formatstr):
"""Print extension data in human-readable form."""
data = messages.format_bytearray(self.data, formatstr)
return "SessionTicketExtension(data={0})".format(data)
tlslite.extensions.TLSExtension._universalExtensions[
ExtensionType.session_ticket] = SessionTicketExtension
class ServerStatusRequestExtension(tlslite.extensions.TLSExtension):
"""Server Status Request extension."""
def __init__(self):
"""Create server status request extension."""
super(ServerStatusRequestExtension, self).__init__(
extType=ExtensionType.status_request)
def parse(self, parser):
"""Deserialise the extension from binary data."""
if parser.getRemainingLength() != 0:
raise SyntaxError() # FIXME
return self
def __repr__(self):
"""Human readable representation of the object."""
return "ServerStatusRequestExtension()"
tlslite.extensions.TLSExtension._serverExtensions[
ExtensionType.status_request] = ServerStatusRequestExtension
class KeyShareExtension(tlslite.extensions.TLSExtension):
"""TLS1.3 extension for handling key negotiation."""
def __init__(self):
"""Create key share extension object."""
super(KeyShareExtension, self).__init__(
extType=ExtensionType.key_share)
self.client_shares = None
def create(self, shares):
"""
Set the list of key shares to send.
@type shares: list of tuples
@param shares: a list of tuples where the first element is a NamedGroup
ID while the second element in a tuple is an opaque bytearray encoding
of the key share.
"""
self.client_shares = shares
return self
@property
def extData(self):
"""Serialise the extension."""
if self.client_shares is None:
return bytearray(0)
writer = Writer()
for group_id, share in self.client_shares:
writer.add(group_id, 2)
if group_id in GroupName.allFF:
share_length_length = 2
else:
share_length_length = 1
writer.addVarSeq(share, 1, share_length_length)
ext_writer = Writer()
ext_writer.add(len(writer.bytes), 2)
ext_writer.bytes += writer.bytes
return ext_writer.bytes
def parse(self, parser):
"""Deserialise the extension."""
if parser.getRemainingLength() == 0:
self.client_shares = None
return
self.client_shares = []
parser.startLengthCheck(2)
while not parser.atLengthCheck():
group_id = parser.get(2)
if group_id in GroupName.allFF:
share_length_length = 2
else:
share_length_length = 1
share = parser.getVarBytes(share_length_length)
self.client_shares.append((group_id, share))
return self
def __repr__(self):
"""Human readble representation of extension."""
return "KeyShareExtension({0!r})".format(self.client_shares)
def __format__(self, formatstr):
"""Formattable representation of extension."""
if self.client_shares is None:
return "KeyShareExtension(None)"
verbose = ""
hexlify = False
if 'v' in formatstr:
verbose = "GroupName."
if 'h' in formatstr:
hexlify = True
shares = []
for group_id, share in self.client_shares:
if hexlify:
share = b2a_hex(share)
else:
share = repr(share)
shares += ["({0}{1}, {2})".format(verbose,
GroupName.toStr(group_id),
share)]
return "KeyShareExtension([" + ",".join(shares) + "])"
tlslite.extensions.TLSExtension._universalExtensions[
ExtensionType.key_share] = KeyShareExtension

256
cscan/messages.py Normal file
View File

@ -0,0 +1,256 @@
# Copyright (c) 2016 Hubert Kario
# Released under Mozilla Public License 2.0
"""Extensions and modification of the tlslite-ng messages classes."""
import tlslite.messages as messages
from tlslite.utils.compat import b2a_hex
from tlslite.constants import ContentType, CertificateType, ECCurveType, \
HashAlgorithm, SignatureAlgorithm
from tlslite.x509certchain import X509CertChain
from tlslite.utils.cryptomath import secureHash
from .constants import HandshakeType, CipherSuite, GroupName
# gotta go fast
# comparing client hello's using ClientHello.write() is painfully slow
# monkey patch in faster compare methods
def __CH_eq_fun(self, other):
"""
Check if the other is equal to the object.
always returns false if other is not a ClientHello object
"""
if not isinstance(other, messages.ClientHello):
return False
return self.ssl2 == other.ssl2 and \
self.client_version == other.client_version and \
self.random == other.random and \
self.session_id == other.session_id and \
self.cipher_suites == other.cipher_suites and \
self.compression_methods == other.compression_methods and \
self.extensions == other.extensions
messages.ClientHello.__eq__ = __CH_eq_fun
def __CH_ne_fun(self, other):
"""
Check if the other is not equal to the object.
always returns true if other is not a ClientHello object
"""
return not self.__eq__(other)
messages.ClientHello.__ne__ = __CH_ne_fun
def format_bytearray(byte_array, formatstr):
"""Format method for bytearrays."""
if 'x' in formatstr:
return b2a_hex(byte_array)
else:
return repr(byte_array)
def format_array(array, formatstr):
"""Return string representation of array while formatting elements."""
if array is None:
return "None"
else:
str_array = []
for elem in array:
if elem.__class__.__format__ is not object.__format__:
str_array += ["{0:{1}}".format(elem, formatstr)]
else:
str_array += [repr(elem)]
return "[" + ", ".join(str_array) + "]"
class ServerHello(messages.ServerHello):
"""Class with enhanced human-readable serialisation."""
def __format__(self, formatstr):
"""Return human readable representation of the object."""
extensions = format_array(self.extensions, formatstr)
random = format_bytearray(self.random, formatstr)
session_id = format_bytearray(self.session_id, formatstr)
cipher_suite = CipherSuite.ietfNames.get(self.cipher_suite,
self.cipher_suite)
if 'v' in formatstr:
cipher_suite = "CipherSuite.{0}".format(cipher_suite)
# TODO cipher_suites (including verbose)
# TODO compression_method (including verbose)
return ("ServerHello(server_version=({0[0]}, {0[1]}), random={1}, "
"session_id={2!r}, cipher_suite={3}, compression_method={4}, "
"_tack_ext={5}, extensions={6})").format(
self.server_version, random, session_id,
cipher_suite, self.compression_method, self._tack_ext,
extensions)
class Certificate(messages.Certificate):
"""Class with more robust certificate parsing and serialisation."""
def parse(self, parser):
"""Deserialise the object from binary data."""
index = parser.index
try:
return super(Certificate, self).parse(parser)
except (AssertionError, SyntaxError):
pass
parser.index = index
parser.startLengthCheck(3)
if self.certificateType == CertificateType.x509:
chainLength = parser.get(3)
index = 0
certificate_list = []
while index != chainLength:
certBytes = parser.getVarBytes(3)
certificate_list.append(certBytes)
index += len(certBytes)+3
if certificate_list:
self.certChain = certificate_list
else:
raise AssertionError()
parser.stopLengthCheck()
return self
def __format__(self, formatstr):
"""Advanced formatting for messages."""
hexify = False
verbose = False
digest = False
if 'h' in formatstr:
hexify = True
if 'v' in formatstr:
verbose = True
if 'm' in formatstr:
digest = True
if self.certChain is None:
cert_list = None
else:
if isinstance(self.certChain, X509CertChain):
cert_list = [cert.bytes for cert in self.certChain.x509List]
else:
cert_list = self.certChain
if digest:
cert_list = "[" + ", ".join(b2a_hex(secureHash(cert, 'sha256'))
for cert in cert_list) + "]"
else:
cert_list = [repr(cert) for cert in cert_list]
return "Certificate({0})".format(cert_list)
class NewSessionTicket(messages.HandshakeMsg):
"""Class for handling the Session Tickets from RFC 5077."""
def __init__(self):
"""Initilize new sesion ticket message object."""
super(NewSessionTicket, self).__init__(HandshakeType.session_ticket)
self.ticket_lifetime_hintt = 0
self.ticket = None
def parse(self, parser):
"""Parse the object from on-the-wire data."""
self.ticket_lifetime_hint = parser.get(4)
self.ticket = parser.getVarBytes(2)
return self
def __format__(self, formatstr):
"""Return human-readable representation of the object."""
ticket = format_bytearray(self.ticket, formatstr)
return "NewSessionTicket(ticket_lifetime_hint={0}, ticket={1})"\
.format(self.ticket_lifetime_hintt, ticket)
class CertificateStatus(messages.HandshakeMsg):
"""Class for handling the CertificateStatus OCSP staples from RFC 4366."""
def __init__(self):
"""Create a certificate status message handling object."""
super(CertificateStatus, self).__init__(
HandshakeType.certificate_status)
self.status_type = 0
self.response = None
def parse(self, parser):
"""Deserialise certificate status message from binary data."""
parser.startLengthCheck(3)
self.status_type = parser.get(1)
if self.status_type == 1: # FIXME, create registry
self.response = parser.getVarBytes(3)
else:
raise SyntaxError() # FIXME, use sane-er type
parser.stopLengthCheck()
return self
def __format__(self, formatstr):
"""Return human-readable representation of certificate status."""
response = format_bytearray(self.response, formatstr)
return "CertificateStatus(status_type={0}, response={1})"\
.format(self.status_type, response)
class Message(messages.Message):
"""Message class with more robust formatting capability."""
def __format__(self, formatstr):
"""Advanced formatting for messages."""
hexify = False
verbose = ""
if 'h' in formatstr:
hexify = True
if 'v' in formatstr:
verbose = "ContentType."
if hexify:
data = b2a_hex(self.data)
else:
data = repr(self.data)
return "Message(contentType={0}{1}, data={2})"\
.format(verbose, ContentType.toStr(self.contentType), data)
class ServerKeyExchange(messages.ServerKeyExchange):
"""ServerKeyExchange class with more robust formatting capability."""
def __format__(self, formatstr):
"""Return human-readable representation of the object."""
if 'v' in formatstr:
verbose = "CipherSuite."
else:
verbose = ""
ret = "ServerKeyExchange(cipherSuite={0}{1}, version={2}"\
.format(verbose, CipherSuite.ietfNames[self.cipherSuite],
self.version)
if self.srp_N:
ret += ", srp_N={0}, srp_g={1}, srp_s={2}, srp_B={3}"\
.format(self.srp_N, self.srp_g, self.srp_s, self.srp_B)
if self.dh_p:
ret += ", dh_p={0}, dh_g={1}, dh_Ys={2}"\
.format(self.dh_p, self.dh_g, self.dh_Ys)
if self.ecdh_Ys:
ecdh_Ys = format_bytearray(self.ecdh_Ys, formatstr)
ret += ", curve_type={0}, named_curve={1}, ecdh_Ys={2}"\
.format(ECCurveType.toStr(self.curve_type),
GroupName.toStr(self.named_curve), ecdh_Ys)
if self.signAlg:
ret += ", hashAlg={0}, signAlg={1}"\
.format(HashAlgorithm.toStr(self.hashAlg),
SignatureAlgorithm.toStr(self.signAlg))
if self.signature:
ret += ", signature={0}"\
.format(format_bytearray(self.signature, formatstr))
return ret + ")"

147
cscan/modifiers.py Normal file
View File

@ -0,0 +1,147 @@
# Copyright (c) 2016 Hubert Kario
# Released under Mozilla Public License 2.0
"""Methods for modifying the scan configurations on the fly."""
from __future__ import print_function
from tlslite.constants import CipherSuite
from tlslite.extensions import SNIExtension, PaddingExtension, TLSExtension
import itertools
def no_sni(generator):
if not generator.extensions:
return generator
generator.extensions[:] = (x for x in generator.extensions
if not isinstance(x, SNIExtension))
generator.modifications.append("no SNI")
return generator
proto_versions = {(3, 0): "SSLv3",
(3, 1): "TLSv1.0",
(3, 2): "TLSv1.1",
(3, 3): "TLSv1.2",
(3, 4): "TLSv1.3",
(3, 5): "TLSv1.4",
(3, 6): "TLSv1.5"}
def version_to_str(version):
"""Convert a version tuple to human-readable string."""
version_name = proto_versions.get(version, None)
if version_name is None:
version_name = "{0[0]}.{0[1]}".format(version)
return version_name
def set_hello_version(generator, version):
"""Set client hello version."""
generator.version = version
generator.modifications += [version_to_str(version)]
return generator
def set_record_version(generator, version):
"""Set record version, un-SSLv2-ify"""
generator.record_version = version
generator.ciphers[:] = (i for i in generator.ciphers if i <= 0xffff)
generator.ssl2 = False
generator.modifications += ["r/{0}".format(version_to_str(version))]
return generator
def no_extensions(generator):
"""Remove extensions"""
generator.extensions = None
generator.modifications += ["no ext"]
return generator
def divceil(divident, divisor):
quot, r = divmod(divident, divisor)
return quot + int(bool(r))
def truncate_ciphers_to_size(generator, size):
"""Truncate list of ciphers until client hello is no bigger than size"""
def cb_fun(client_hello, size=size):
hello_len = len(client_hello.write())
bytes_to_remove = hello_len - size
if bytes_to_remove > 0:
ciphers_to_remove = divceil(bytes_to_remove, 2)
client_hello.cipher_suites[:] = \
client_hello.cipher_suites[:-ciphers_to_remove]
return client_hello
generator.callbacks.append(cb_fun)
generator.modifications += ["trunc c/{0}".format(size)]
return generator
def append_ciphers_to_size(generator, size):
"""
Add ciphers from the 0x2000-0xa000 range until size is reached
Increases the size of the Client Hello message until it is at least
`size` bytes long. Uses cipher ID's from the 0x2000-0xc000 range to do
it (0x5600, a.k.a TLS_FALLBACK_SCSV, excluded)
"""
def cb_fun(client_hello, size=size):
ciphers_iter = iter(range(0x2000, 0xc000))
ciphers_present = set(client_hello.cipher_suites)
# we don't want to add a cipher id with special meaning
# and the set is used only internally
ciphers_present.add(CipherSuite.TLS_FALLBACK_SCSV)
bytes_to_add = size - len(client_hello.write())
if bytes_to_add > 0:
ciphers_to_add = divceil(bytes_to_add, 2)
ciphers_gen = (x for x in ciphers_iter
if x not in ciphers_present)
client_hello.cipher_suites.extend(itertools.islice(ciphers_gen,
ciphers_to_add))
return client_hello
generator.callbacks.append(cb_fun)
generator.modifications += ["append c/{0}".format(size)]
return generator
def extend_with_ext_to_size(generator, size):
"""
Add the padding extension so that the Hello is at least `size` bytes
Either adds a padding extension or extends an existing one so that
the specified size is reached
"""
def cb_fun(client_hello, size=size):
if len(client_hello.write()) > size:
return client_hello
if not client_hello.extensions:
client_hello.extensions = []
ext = next((x for x in client_hello.extensions
if isinstance(x, PaddingExtension)), None)
if not ext:
ext = PaddingExtension()
client_hello.extensions.append(ext)
# check if just adding the extension, with no payload, haven't pushed
# us over the limit
bytes_to_add = size - len(client_hello.write())
if bytes_to_add > 0:
ext.paddingData += bytearray(bytes_to_add)
return client_hello
generator.callbacks.append(cb_fun)
generator.modifications += ["append e/{0}".format(size)]
return generator
def add_empty_ext(generator, ext_type):
if generator.extensions is None:
generator.extensions = []
generator.extensions += [TLSExtension(extType=ext_type)
.create(bytearray(0))]
generator.modifications += ["add ext {0}".format(ext_type)]
return generator

157
cscan/scanner.py Normal file
View File

@ -0,0 +1,157 @@
# Copyright (c) 2016 Hubert Kario
# Released under the Mozilla Public License 2.0
"""Classes used for scanning servers and getting their responses."""
import socket
from .constants import CipherSuite, HandshakeType
from .messages import Certificate, ServerHello, Message, NewSessionTicket, \
CertificateStatus, ServerKeyExchange
from tlslite.constants import CertificateType, ContentType
from tlslite.messages import \
CertificateRequest, NextProtocol, ServerHelloDone, Alert
from tlslite.defragmenter import Defragmenter
from tlslite.messagesocket import MessageSocket
from tlslite.errors import TLSAbruptCloseError, TLSIllegalParameterException
class HandshakeParser(object):
"""Inteligent parser for handshake messages."""
def __init__(self, version=(3, 1),
cipher_suite=CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
certificate_type=CertificateType.x509):
"""Initialize parser object."""
self.version = version
self.cipher_suite = cipher_suite
self.certificate_type = certificate_type
def parse(self, parser):
"""Parse a handshake message."""
hs_type = parser.get(1)
if hs_type == HandshakeType.server_hello:
msg = ServerHello().parse(parser)
self.version = msg.server_version
self.cipher_suite = msg.cipher_suite
self.certificate_type = msg.certificate_type
return msg
elif hs_type == HandshakeType.certificate:
msg = Certificate(self.certificate_type)
elif hs_type == HandshakeType.server_key_exchange:
msg = ServerKeyExchange(self.cipher_suite, self.version)
elif hs_type == HandshakeType.certificate_request:
msg = CertificateRequest(self.version)
elif hs_type == HandshakeType.next_protocol:
msg = NextProtocol().parse(parser)
elif hs_type == HandshakeType.server_hello_done:
msg = ServerHelloDone()
elif hs_type == HandshakeType.session_ticket:
msg = NewSessionTicket()
elif hs_type == HandshakeType.certificate_status:
msg = CertificateStatus()
else:
raise ValueError("Unknown handshake type: {0}".format(hs_type))
# don't abort when we can't parse a message, save it as unparsed
try:
msg.parse(parser)
except SyntaxError:
msg = Message(ContentType.handshake, parser.bytes)
return msg
class Scanner(object):
"""Helper class for scanning a host and returning serialised responses."""
def __init__(self, hello_gen, host, port=443, hostname=None):
"""Initialize scanner."""
self.host = host
self.hello_gen = hello_gen
self.port = port
self.hostname = hostname
def scan(self):
"""Perform a scan on server."""
defragger = Defragmenter()
defragger.addStaticSize(ContentType.change_cipher_spec, 1)
defragger.addStaticSize(ContentType.alert, 2)
defragger.addDynamicSize(ContentType.handshake, 1, 3)
try:
raw_sock = socket.create_connection((self.host, self.port), 5)
except socket.error as e:
return [e]
sock = MessageSocket(raw_sock, defragger)
if self.hostname is not None:
client_hello = self.hello_gen(bytearray(self.hostname,
'utf-8'))
else:
client_hello = self.hello_gen(None)
# record layer version - TLSv1.x
# use the version from configuration, if present, or default to the
# RFC recommended (3, 1) for TLS and (3, 0) for SSLv3
if hasattr(client_hello, 'record_version'):
sock.version = client_hello.record_version
elif hasattr(self.hello_gen, 'record_version'):
sock.version = self.hello_gen.record_version
elif client_hello.client_version > (3, 1): # TLS1.0
sock.version = (3, 1)
else:
sock.version = client_hello.client_version
# we don't want to send invalid messages (SSLv2 hello in SSL record
# layer), so set the record layer version to SSLv2 if the hello is
# of SSLv2 format
if client_hello.ssl2:
sock.version = (0, 2)
# save the record version used in the end for later analysis
client_hello.record_version = sock.version
messages = [client_hello]
handshake_parser = HandshakeParser()
try:
sock.sendMessageBlocking(client_hello)
except socket.error as e:
messages.append(e)
return messages
except TLSAbruptCloseError as e:
sock.sock.close()
messages.append(e)
return messages
# get all the server messages that affect connection, abort as soon
# as they've been read
try:
while True:
header, parser = sock.recvMessageBlocking()
if header.type == ContentType.alert:
alert = Alert()
alert.parse(parser)
alert.record_version = header.version
messages += [alert]
elif header.type == ContentType.handshake:
msg = handshake_parser.parse(parser)
msg.record_version = header.version
messages += [msg]
if isinstance(msg, ServerHelloDone):
return messages
else:
raise TypeError("Unknown content type: {0}"
.format(header.type))
except (TLSAbruptCloseError, TLSIllegalParameterException,
ValueError, TypeError, socket.error, SyntaxError) as e:
messages += [e]
return messages
finally:
try:
sock.sock.close()
except (socket.error, OSError):
pass

0
cscan_tests/__init__.py Normal file
View File

View File

@ -0,0 +1,258 @@
# Copyright (c) 2015 Hubert Kario
# Released under Mozilla Public License Version 2.0
try:
import unittest2 as unittest
except ImportError:
import unittest
from tlslite.extensions import SignatureAlgorithmsExtension, SNIExtension, \
SupportedGroupsExtension, ECPointFormatsExtension, TLSExtension
from cscan.config import HugeCipherList, VeryCompatible, IE_8_Win_XP
from cscan.bisector import bisect_lists, list_union, Bisect
from cscan.modifiers import extend_with_ext_to_size, append_ciphers_to_size
class TestListUnion(unittest.TestCase):
def test_identical(self):
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 4])
def test_extended(self):
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5, 6, 7, 8]
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 4, 5, 6, 7, 8])
def test_extended_reversed(self):
a = [1, 2, 3, 4, 5, 6, 7, 8]
b = [1, 2, 3, 4]
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 4, 5, 6, 7, 8])
def test_prepended(self):
a = [5, 6, 7, 8]
b = [1, 2, 3, 4, 5, 6, 7, 8]
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 4, 5, 6, 7, 8])
def test_mixed(self):
a = [1, 2, 3, 4]
b = [5, 1, 2, 6, 4]
c = list_union(a, b)
self.assertEqual(c, [5, 1, 2, 3, 6, 4])
def test_mixed_reversed(self):
a = [5, 1, 2, 6, 4]
b = [1, 2, 3, 4]
c = list_union(a, b)
self.assertEqual(c, [5, 1, 2, 6, 3, 4])
def test_different_order(self):
a = [1, 2, 3, 4]
b = [2, 3, 1, 4]
c = list_union(a, b)
self.assertEqual(c, [2, 3, 1, 4])
def test_different_order2(self):
a = [1, 2, 3, 4, 5, 6]
b = [3, 1, 4, 2, 5, 6]
c = list_union(a, b)
self.assertEqual(c, [3, 1, 4, 2, 5, 6])
def test_different_order_superset(self):
a = [1, 2, 3, 4]
b = [4, 3, 1, 2, 5, 6]
c = list_union(a, b)
self.assertEqual(c, [4, 3, 1, 2, 5, 6])
def test_completely_disjoint(self):
a = [1, 2, 3, 4]
b = [5, 6, 7, 8]
c = list_union(a, b)
self.assertEqual(c, [1, 5, 2, 6, 3, 7, 4, 8])
def test_different_suffix(self):
a = [1, 2, 3, 4]
b = [1, 2, 5, 6]
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 5, 4, 6])
def test_different_prefix(self):
a = [1, 2, 3, 4]
b = [5, 6, 3, 4]
c = list_union(a, b)
self.assertEqual(c, [1, 5, 2, 6, 3, 4])
def test_one_empty(self):
a = [1, 2, 3, 4]
b = []
c = list_union(a, b)
self.assertEqual(c, [1, 2, 3, 4])
def test_both_empty(self):
a = []
b = []
c = list_union(a, b)
self.assertEqual(c, [])
class TestBisectLists(unittest.TestCase):
def test_sorted(self):
a = [1, 5, 7, 9]
b = [3, 5, 6, 8]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 3, 5, 7])
d = bisect_lists(c, b)
self.assertEqual(d, [1, 3, 5, 7])
e = bisect_lists(a, c)
self.assertEqual(e, [1, 3, 5, 7])
def test_extended(self):
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5, 6, 7, 8]
c = bisect_lists(a, b)
self.assertEqual(a, [1, 2, 3, 4])
self.assertEqual(b, [1, 2, 3, 4, 5, 6, 7, 8])
self.assertEqual(c, [1, 2, 3, 4, 5, 6])
d = bisect_lists(c, b)
self.assertEqual(d, [1, 2, 3, 4, 5, 6, 7])
def test_extended_reversed(self):
a = [1, 2, 3, 4, 5, 6, 7, 8]
b = [1, 2, 3, 4]
c = bisect_lists(a, b)
self.assertEqual(a, [1, 2, 3, 4, 5, 6, 7, 8])
self.assertEqual(b, [1, 2, 3, 4])
self.assertEqual(c, [1, 2, 3, 4, 5, 6])
def test_prepended(self):
a = [5, 6, 7, 8]
b = [1, 2, 3, 4, 5, 6, 7, 8]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2, 5, 6, 7, 8])
def test_both_different(self):
a = [1, 2, 3, 4]
b = [1, 2, 5, 6]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2, 3, 5])
def test_small_difference(self):
a = [1, 2, 3, 4]
b = [1, 2, 3, 5]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2, 3, 4])
def test_small_difference_with_different_order(self):
a = [2, 3, 1, 4]
b = [1, 2, 3, 5]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2, 3, 5])
def test_one_empty(self):
a = []
b = [1, 2, 3, 4]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2])
def test_both_empty(self):
a = []
b = []
c = bisect_lists(a, b)
self.assertEqual(c, [])
def test_one_None(self):
a = None
b = [1, 2, 3, 4]
c = bisect_lists(a, b)
self.assertEqual(c, [1, 2])
def test_short_and_None(self):
a = None
b = [1]
c = bisect_lists(a, b)
self.assertEqual(c, [])
def test_empty_and_None(self):
a = None
b = []
c = bisect_lists(a, b)
self.assertEqual(c, None)
class TestBisect(unittest.TestCase):
def test___init__(self):
b = Bisect(None, None, None, None)
self.assertIsNotNone(b)
def test_run(self):
def test_cb(hello):
return len(hello.write()) <= 2**14
bad = HugeCipherList()
good = VeryCompatible()
self.assertGreater(len(bad(b'').write()), 2**14)
self.assertLess(len(good(b'').write()), 2**14)
bi = Bisect(good, bad, "localhost", test_cb)
a, b = bi.run()
self.assertEqual(len(a.write()), 2**14-1)
self.assertEqual(len(b.write()), 2**14+1)
def test_run_with_extensions(self):
def test_cb(hello):
if not hello.extensions:
return True
a = next((x for x in hello.extensions
if isinstance(x, SignatureAlgorithmsExtension)), None)
return a is None
good = IE_8_Win_XP()
bad = VeryCompatible()
self.assertTrue(test_cb(good(b'localhost')))
self.assertFalse(test_cb(bad(b'localhost')))
bi = Bisect(good, bad, "localhost", test_cb)
a, b = bi.run()
ext = next((x for x in a.extensions
if isinstance(x, SignatureAlgorithmsExtension)), None)
self.assertIsNone(ext)
ext = next((x for x in b.extensions
if isinstance(x, SignatureAlgorithmsExtension)), None)
self.assertIsNotNone(ext)
def test_run_with_extension_size(self):
def test_cb(hello):
return len(hello.write()) <= 2**14
bad = extend_with_ext_to_size(VeryCompatible(), 2**16)
good = VeryCompatible()
bi = Bisect(good, bad, "localhost", test_cb)
a, b = bi.run()
self.assertEqual(len(a.write()), 2**14)
self.assertEqual(len(b.write()), 2**14+1)
def test_run_with_ext_and_ciphers(self):
def test_cb(hello):
return len(hello.write()) <= 2**14
bad = extend_with_ext_to_size(VeryCompatible(), 2**15)
bad = append_ciphers_to_size(bad, 2**16)
good = VeryCompatible()
bi = Bisect(good, bad, "localhost", test_cb)
a, b = bi.run()
self.assertEqual(len(a.write()), 2**14)
self.assertEqual(len(b.write()), 2**14+1)

View File

@ -0,0 +1,98 @@
# Copyright (c) 2015 Hubert Kario
# Released under Mozilla Public License Version 2.0
try:
import unittest2 as unittest
except ImportError:
import unittest
from tlslite.messages import ClientHello
from tlslite.extensions import SNIExtension, SupportedGroupsExtension, \
ECPointFormatsExtension, NPNExtension, SignatureAlgorithmsExtension
from tlslite.utils.codec import Parser
from cscan.config import Firefox_42, Xmas_tree, Firefox_46
from cscan.extensions import RenegotiationExtension
from cscan.constants import ExtensionType
class TestFirefox(unittest.TestCase):
def test_firefox_42(self):
gen = Firefox_42()
ch = gen(bytearray(b'example.com'))
self.assertIsNotNone(ch)
self.assertIsInstance(ch, ClientHello)
self.assertEqual(len(ch.write()), 176)
self.assertEqual(ch.client_version, (3, 3))
self.assertEqual(gen.record_version, (3, 1))
self.assertEqual(len(ch.cipher_suites), 11)
self.assertIsInstance(ch.extensions[0], SNIExtension)
self.assertEqual(ch.extensions[1].extType,
ExtensionType.renegotiation_info)
self.assertIsInstance(ch.extensions[2],
SupportedGroupsExtension)
self.assertIsInstance(ch.extensions[3],
ECPointFormatsExtension)
self.assertEqual(ch.extensions[4].extType,
ExtensionType.session_ticket)
# bug in tlslite-ng, removes NPN extensions from provided extensions
#self.assertIsInstance(ch.extensions[5],
# NPNExtension)
self.assertEqual(ch.extensions[5].extType,
ExtensionType.alpn)
self.assertEqual(ch.extensions[6].extType,
ExtensionType.status_request)
self.assertIsInstance(ch.extensions[7],
SignatureAlgorithmsExtension)
self.assertEqual(ch.compression_methods, [0])
def test_firefox_46(self):
gen = Firefox_46()
ch = gen(bytearray(b'example.com'))
self.assertIsNotNone(ch)
self.assertIsInstance(ch, ClientHello)
self.assertEqual(len(ch.write()), 180)
self.assertEqual(ch.client_version, (3, 3))
self.assertEqual(gen.record_version, (3, 1))
self.assertEqual(len(ch.cipher_suites), 11)
self.assertIsInstance(ch.extensions[0], SNIExtension)
self.assertEqual(ch.extensions[1].extType,
ExtensionType.extended_master_secret)
self.assertEqual(ch.extensions[2].extType,
ExtensionType.renegotiation_info)
self.assertIsInstance(ch.extensions[3],
SupportedGroupsExtension)
self.assertIsInstance(ch.extensions[4],
ECPointFormatsExtension)
self.assertEqual(ch.extensions[5].extType,
ExtensionType.session_ticket)
# bug in tlslite-ng, removes NPN extensions from provided extensions
#self.assertIsInstance(ch.extensions[6],
# NPNExtension)
self.assertEqual(ch.extensions[6].extType,
ExtensionType.alpn)
self.assertEqual(ch.extensions[7].extType,
ExtensionType.status_request)
self.assertIsInstance(ch.extensions[8],
SignatureAlgorithmsExtension)
self.assertEqual(ch.compression_methods, [0])
class TestXmasTree(unittest.TestCase):
def test_xmas_tree_tls_1_3(self):
ch = Xmas_tree()(bytearray(b'example.com'))
self.assertIsNotNone(ch)
self.assertIsInstance(ch, ClientHello)
self.assertEqual(len(ch.write()), 2792)
def test_xmas_tree_tls_1_3_parse(self):
ch = Xmas_tree()(bytearray(b'example.com'))
parser = Parser(ch.write()[1:])
client_hello = ClientHello()
client_hello.parse(parser)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,76 @@
# Copyright (c) 2015 Hubert Kario
# Released under Mozilla Public License Version 2.0
try:
import unittest2 as unittest
except ImportError:
import unittest
from tlslite.utils.codec import Parser
from cscan.extensions import KeyShareExtension
from cscan.constants import GroupName
class TestKeyShareExtension(unittest.TestCase):
def test___init__(self):
ext = KeyShareExtension()
self.assertIsNotNone(ext)
def test_create(self):
ext = KeyShareExtension()
ext.create([(1, bytearray(b'\x12')),
(2, bytearray(b'\x33'))])
self.assertEqual(ext.client_shares, [(1, bytearray(b'\x12')),
(2, bytearray(b'\x33'))])
def test_write(self):
ext = KeyShareExtension()
ext.create([(GroupName.secp256r1, bytearray(b'\xff\xfa')),
(GroupName.ffdhe2048, bytearray(b'\xaf\xaa'))])
data = ext.write()
self.assertEqual(data, bytearray(
b'\x00\x2a\x00\x0d'
b'\x00\x0b'
b'\x00\x17\x02\xff\xfa'
b'\x01\x00\x00\x02\xaf\xaa'))
def test_write_with_no_data(self):
ext = KeyShareExtension()
data = ext.write()
self.assertEqual(data, bytearray(b'\x00\x2a\x00\x00'))
def test_parse(self):
parser = Parser(bytearray(
#b'\x00\x2a\x00\x0d'
b'\x00\x0b'
b'\x00\x17\x02\xff\xfa'
b'\x01\x00\x00\x02\xaf\xaa'))
ext = KeyShareExtension()
ext.parse(parser)
self.assertEqual(ext.client_shares,
[(GroupName.secp256r1, bytearray(b'\xff\xfa')),
(GroupName.ffdhe2048, bytearray(b'\xaf\xaa'))])
def test_parse_with_no_data(self):
parser = Parser(bytearray())
ext = KeyShareExtension()
ext.parse(parser)
self.assertIsNone(ext.client_shares)
def test___repr__(self):
ext = KeyShareExtension()
ext.create([(1, bytearray(b'\xff'))])
self.assertEqual("KeyShareExtension([(1, bytearray(b\'\\xff\'))])",
repr(ext))

View File

@ -0,0 +1,45 @@
# Copyright (c) 2015 Hubert Kario
# Released under Mozilla Public License Version 2.0
try:
import unittest2 as unittest
except ImportError:
import unittest
from cscan.config import HugeCipherList, Firefox_42
from cscan.modifiers import truncate_ciphers_to_size, append_ciphers_to_size, \
extend_with_ext_to_size
class TestTruncateCiphersToSize(unittest.TestCase):
def test_with_big_hello(self):
gen = HugeCipherList()
self.assertGreater(len(gen(b'localhost').write()), 2**14)
self.assertEqual(gen(b'localhost').cipher_suites[0], 49196)
gen = truncate_ciphers_to_size(gen, 2**12)
self.assertEqual(len(gen(b'localhost').write()), 2**12-1)
self.assertEqual(gen(b'localhost').cipher_suites[0], 49196)
class TestAppendCiphersToSize(unittest.TestCase):
def test_with_small_hello(self):
gen = Firefox_42()
self.assertLess(len(gen(b'localhost').write()), 2**10)
self.assertEqual(gen(b'localhost').cipher_suites[0], 49195)
gen = append_ciphers_to_size(gen, 2**12)
self.assertEqual(len(gen(b'localhost').write()), 2**12)
self.assertEqual(gen(b'localhost').cipher_suites[0], 49195)
class TestExtendWithExtToSize(unittest.TestCase):
def test_with_small_hello(self):
gen = Firefox_42()
self.assertLess(len(gen(b'localhost').write()), 2**10)
gen = extend_with_ext_to_size(gen, 2**12)
self.assertEqual(len(gen(b'localhost').write()), 2**12)

View File

@ -114,6 +114,7 @@ ecccurve = defaultdict(int)
npn = defaultdict(int) npn = defaultdict(int)
ocspstaple = defaultdict(int) ocspstaple = defaultdict(int)
fallbacks = defaultdict(int) fallbacks = defaultdict(int)
intolerancies = defaultdict(int)
# array with indexes of fallback names for the matrix report # array with indexes of fallback names for the matrix report
fallback_ids = defaultdict(int) fallback_ids = defaultdict(int)
i=0 i=0
@ -177,6 +178,7 @@ for r,d,flist in os.walk(path):
tempecccurve = {} tempecccurve = {}
tempnpn = {} tempnpn = {}
tempfallbacks = {} tempfallbacks = {}
tempintolerancies = {}
""" supported ciphers by the server under scan """ """ supported ciphers by the server under scan """
tempcipherstats = {} tempcipherstats = {}
temppfssigalgordering = {} temppfssigalgordering = {}
@ -351,6 +353,21 @@ for r,d,flist in os.walk(path):
except KeyError: except KeyError:
pass pass
if 'intolerancies' in results:
intoler = results['intolerancies']
for name, val in intoler.items():
if val is True:
tempintolerancies[name] = 1
size_intol = [x.replace('size ', '')
for x in tempintolerancies.keys()
if x.startswith('size ')]
if size_intol:
size_intol.sort(reverse=True)
tempintolerancies['size {0}'
.format(" ".join(size_intol))] = 1
else:
tempintolerancies['x:missing information'] = 1
""" get some extra data about server """ """ get some extra data about server """
if 'renegotiation' in results: if 'renegotiation' in results:
temprenegotiation[results['renegotiation']] = 1 temprenegotiation[results['renegotiation']] = 1
@ -582,6 +599,9 @@ for r,d,flist in os.walk(path):
for s in tempfallbacks: for s in tempfallbacks:
fallbacks[s] += 1 fallbacks[s] += 1
for s in tempintolerancies:
intolerancies[s] += 1
for s in tempsigstats: for s in tempsigstats:
sigalg[s] += 1 sigalg[s] += 1
@ -920,3 +940,9 @@ print("------------------------")
fallback_ids_sorted=sorted(fallback_ids.items(), key=operator.itemgetter(1)) fallback_ids_sorted=sorted(fallback_ids.items(), key=operator.itemgetter(1))
for touple in fallback_ids_sorted: for touple in fallback_ids_sorted:
print(str(touple[1]+1).rjust(3) + " " + str(touple[0])) print(str(touple[1]+1).rjust(3) + " " + str(touple[0]))
print("\nClient Hello intolerance Count Percent")
print("----------------------------------------+---------+-------")
for stat in sorted(intolerancies):
percent = round(intolerancies[stat] / total * 100, 4)
sys.stdout.write(stat.ljust(40) + " " + str(intolerancies[stat]).ljust(10) + str(percent).ljust(4) + "\n")