mirror of
https://github.com/mozilla/cipherscan.git
synced 2025-04-21 01:03:39 +02:00
Merge 1f26852b29
into e5b747d29b
This commit is contained in:
commit
06f2eb2eb6
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,2 +1,10 @@
|
|||||||
mozilla/*
|
mozilla/*
|
||||||
top1m/results/*
|
top1m/results/*
|
||||||
|
tlslite
|
||||||
|
.tlslite-ng
|
||||||
|
*/__pycache__
|
||||||
|
*.pyc
|
||||||
|
.coverage
|
||||||
|
.python-ecdsa
|
||||||
|
ecdsa
|
||||||
|
tlslite
|
||||||
|
20
cipherscan
20
cipherscan
@ -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
467
cscan.py
Normal 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
26
cscan.sh
Executable 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
0
cscan/__init__.py
Normal file
172
cscan/bisector.py
Normal file
172
cscan/bisector.py
Normal 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
580
cscan/config.py
Normal 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
165
cscan/constants.py
Normal 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
200
cscan/extensions.py
Normal 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
256
cscan/messages.py
Normal 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
147
cscan/modifiers.py
Normal 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
157
cscan/scanner.py
Normal 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
0
cscan_tests/__init__.py
Normal file
258
cscan_tests/test_bisector.py
Normal file
258
cscan_tests/test_bisector.py
Normal 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)
|
98
cscan_tests/test_config.py
Normal file
98
cscan_tests/test_config.py
Normal 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()
|
76
cscan_tests/test_extensions.py
Normal file
76
cscan_tests/test_extensions.py
Normal 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))
|
45
cscan_tests/test_modifiers.py
Normal file
45
cscan_tests/test_modifiers.py
Normal 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)
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user