-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #649 from inikolcev/hello-request
Add support for HelloRequest
- Loading branch information
Showing
8 changed files
with
488 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,350 @@ | ||
# Author: Hubert Kario, (c) 2018 | ||
# Released under Gnu GPL v2.0, see LICENSE file for details | ||
|
||
from __future__ import print_function | ||
import traceback | ||
import sys | ||
import getopt | ||
from itertools import chain | ||
from random import sample | ||
|
||
from tlsfuzzer.runner import Runner | ||
from tlsfuzzer.messages import Connect, ClientHelloGenerator, \ | ||
ClientKeyExchangeGenerator, ChangeCipherSpecGenerator, \ | ||
FinishedGenerator, ApplicationDataGenerator, AlertGenerator, \ | ||
fuzz_message, ResetHandshakeHashes, Close, ResetRenegotiationInfo, \ | ||
CertificateGenerator, CertificateVerifyGenerator | ||
from tlsfuzzer.expect import ExpectServerHello, ExpectCertificate, \ | ||
ExpectServerHelloDone, ExpectChangeCipherSpec, ExpectFinished, \ | ||
ExpectAlert, ExpectApplicationData, ExpectClose, ExpectServerKeyExchange,\ | ||
ExpectNoMessage, ExpectCertificateRequest, ExpectHelloRequest | ||
|
||
from tlslite.constants import CipherSuite, AlertLevel, AlertDescription, \ | ||
ExtensionType, SignatureScheme, GroupName | ||
from tlslite.extensions import ALPNExtension, TLSExtension, \ | ||
SignatureAlgorithmsExtension, SignatureAlgorithmsCertExtension, \ | ||
SupportedGroupsExtension | ||
from tlslite.utils.keyfactory import parsePEMKey | ||
from tlslite.x509 import X509 | ||
from tlslite.x509certchain import X509CertChain | ||
from tlsfuzzer.utils.lists import natural_sort_keys | ||
|
||
|
||
version = 2 | ||
|
||
|
||
def help_msg(): | ||
print("Usage: <script-name> [-h hostname] [-p port] [[probe-name] ...]") | ||
print(" -h hostname name of the host to run the test against") | ||
print(" localhost by default") | ||
print(" -p port port number to use for connection, 4433 by default") | ||
print(" probe-name if present, will run only the probes with given") | ||
print(" names and not all of them, e.g \"sanity\"") | ||
print(" -d Use (EC)DHE instead of RSA for key exchange") | ||
print(" -e probe-name exclude the probe from the list of the ones run") | ||
print(" may be specified multiple times") | ||
print(" -n num only run `num` random tests instead of a full set") | ||
print(" (excluding \"sanity\" tests)") | ||
print(" --no-ins-renego expect the insecure renegotiation to be unsupported") | ||
print(" --early-abort server aborts as soon as we ask for restricted resource") | ||
print(" -k file.pem file with private key for client") | ||
print(" -c file.pem file with certificate for client") | ||
print(" --help this message") | ||
|
||
|
||
def main(): | ||
host = "localhost" | ||
port = 4433 | ||
num_limit = None | ||
run_exclude = set() | ||
no_ins_renego = False | ||
private_key = None | ||
cert = None | ||
early_abort = False | ||
dhe = False | ||
|
||
argv = sys.argv[1:] | ||
opts, args = getopt.getopt(argv, "h:p:e:n:k:c:d", ["help", "no-ins-renego", | ||
"early-abort"]) | ||
for opt, arg in opts: | ||
if opt == '-h': | ||
host = arg | ||
elif opt == '-p': | ||
port = int(arg) | ||
elif opt == '-e': | ||
run_exclude.add(arg) | ||
elif opt == '-d': | ||
dhe = True | ||
elif opt == '-n': | ||
num_limit = int(arg) | ||
elif opt == '--no-ins-renego': | ||
no_ins_renego = True | ||
elif opt == '--help': | ||
help_msg() | ||
sys.exit(0) | ||
elif opt == '--early-abort': | ||
early_abort = True | ||
elif opt == '-k': | ||
text_key = open(arg, 'rb').read() | ||
if sys.version_info[0] >= 3: | ||
text_key = str(text_key, 'utf-8') | ||
private_key = parsePEMKey(text_key, private=True) | ||
elif opt == '-c': | ||
text_cert = open(arg, 'rb').read() | ||
if sys.version_info[0] >= 3: | ||
text_cert = str(text_cert, 'utf-8') | ||
cert = X509() | ||
cert.parse(text_cert) | ||
else: | ||
raise ValueError("Unknown option: {0}".format(opt)) | ||
|
||
if not private_key: | ||
raise ValueError("Specify private key file using -k") | ||
if not cert: | ||
raise ValueError("Specify certificate file using -c") | ||
|
||
if args: | ||
run_only = set(args) | ||
else: | ||
run_only = None | ||
|
||
conversations = {} | ||
|
||
conversation = Connect(host, port) | ||
node = conversation | ||
sig_algs = [SignatureScheme.rsa_pss_rsae_sha256, | ||
SignatureScheme.rsa_pss_pss_sha256, | ||
SignatureScheme.rsa_pkcs1_sha256, | ||
SignatureScheme.ecdsa_secp256r1_sha256] | ||
if dhe: | ||
ext = {} | ||
groups = [GroupName.secp256r1, | ||
GroupName.ffdhe2048] | ||
ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\ | ||
.create(groups) | ||
ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV] | ||
else: | ||
ext = {} | ||
ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV] | ||
ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\ | ||
.create(sig_algs) | ||
ext[ExtensionType.signature_algorithms_cert] = \ | ||
SignatureAlgorithmsCertExtension().create(sig_algs) | ||
node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext)) | ||
node = node.add_child(ExpectServerHello()) | ||
node = node.add_child(ExpectCertificate()) | ||
if dhe: | ||
node = node.add_child(ExpectServerKeyExchange()) | ||
node = node.add_child(ExpectServerHelloDone()) | ||
node = node.add_child(ClientKeyExchangeGenerator()) | ||
node = node.add_child(ChangeCipherSpecGenerator()) | ||
node = node.add_child(FinishedGenerator()) | ||
node = node.add_child(ExpectChangeCipherSpec()) | ||
node = node.add_child(ExpectFinished()) | ||
node = node.add_child(ApplicationDataGenerator( | ||
bytearray(b"GET /?Renegotiation_Test=tlsfuzzer HTTP/1.0\r\n\r\n"))) | ||
node = node.add_child(ExpectApplicationData()) | ||
node = node.add_child(AlertGenerator(AlertLevel.warning, | ||
AlertDescription.close_notify)) | ||
node = node.add_child(ExpectAlert()) | ||
node.next_sibling = ExpectClose() | ||
conversations["sanity"] = conversation | ||
|
||
# renegotiation | ||
conversation = Connect(host, port) | ||
node = conversation | ||
|
||
sig_algs = [SignatureScheme.rsa_pss_rsae_sha256, | ||
SignatureScheme.rsa_pss_pss_sha256, | ||
SignatureScheme.rsa_pkcs1_sha256, | ||
SignatureScheme.ecdsa_secp256r1_sha256] | ||
if dhe: | ||
ext = {ExtensionType.renegotiation_info:None} | ||
groups = [GroupName.secp256r1, | ||
GroupName.ffdhe2048] | ||
ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\ | ||
.create(groups) | ||
ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA] | ||
else: | ||
ext = {ExtensionType.renegotiation_info:None} | ||
ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA] | ||
ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\ | ||
.create(sig_algs) | ||
ext[ExtensionType.signature_algorithms_cert] = \ | ||
SignatureAlgorithmsCertExtension().create(sig_algs) | ||
node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext)) | ||
ext = {ExtensionType.renegotiation_info: None} | ||
node = node.add_child(ExpectServerHello(extensions=ext)) | ||
node = node.add_child(ExpectCertificate()) | ||
if dhe: | ||
node = node.add_child(ExpectServerKeyExchange()) | ||
node = node.add_child(ExpectServerHelloDone()) | ||
node = node.add_child(ClientKeyExchangeGenerator()) | ||
node = node.add_child(ChangeCipherSpecGenerator()) | ||
node = node.add_child(FinishedGenerator()) | ||
node = node.add_child(ExpectChangeCipherSpec()) | ||
node = node.add_child(ExpectFinished()) | ||
# send GET request | ||
node = node.add_child(ApplicationDataGenerator( | ||
bytearray(b"GET /secure/test HTTP/1.0\r\n\r\n"))) | ||
# 2nd handshake | ||
node = node.add_child(ExpectHelloRequest()) | ||
node = node.add_child(ResetHandshakeHashes()) | ||
ext = {ExtensionType.renegotiation_info:None} | ||
ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\ | ||
.create(sig_algs) | ||
ext[ExtensionType.signature_algorithms_cert] = \ | ||
SignatureAlgorithmsCertExtension().create(sig_algs) | ||
node = node.add_child(ClientHelloGenerator(ciphers, | ||
session_id=bytearray(0), | ||
extensions=ext)) | ||
ext = {ExtensionType.renegotiation_info: None} | ||
node = node.add_child(ExpectServerHello(extensions=ext)) | ||
node = node.add_child(ExpectCertificate()) | ||
if dhe: | ||
node = node.add_child(ExpectServerKeyExchange()) | ||
node = node.add_child(ExpectCertificateRequest()) | ||
node = node.add_child(ExpectServerHelloDone()) | ||
node = node.add_child(CertificateGenerator(X509CertChain([cert]))) | ||
node = node.add_child(ClientKeyExchangeGenerator()) | ||
node = node.add_child(CertificateVerifyGenerator(private_key)) | ||
node = node.add_child(ChangeCipherSpecGenerator()) | ||
node = node.add_child(FinishedGenerator()) | ||
node = node.add_child(ExpectChangeCipherSpec()) | ||
node = node.add_child(ExpectFinished()) | ||
node = node.add_child(ExpectApplicationData()) | ||
conversations["try secure renegotiation"] = conversation | ||
|
||
# insecure renegotiation | ||
conversation = Connect(host, port) | ||
node = conversation | ||
sig_algs = [SignatureScheme.rsa_pss_rsae_sha256, | ||
SignatureScheme.rsa_pss_pss_sha256, | ||
SignatureScheme.rsa_pkcs1_sha256, | ||
SignatureScheme.ecdsa_secp256r1_sha256] | ||
if dhe: | ||
ext = {ExtensionType.renegotiation_info:None} | ||
groups = [GroupName.secp256r1, | ||
GroupName.ffdhe2048] | ||
ext[ExtensionType.supported_groups] = SupportedGroupsExtension()\ | ||
.create(groups) | ||
ciphers = [CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, | ||
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA] | ||
else: | ||
ext = {ExtensionType.renegotiation_info:None} | ||
ciphers = [CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA] | ||
ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\ | ||
.create(sig_algs) | ||
ext[ExtensionType.signature_algorithms_cert] = \ | ||
SignatureAlgorithmsCertExtension().create(sig_algs) | ||
node = node.add_child(ClientHelloGenerator(ciphers, extensions=ext)) | ||
node = node.add_child(ExpectServerHello()) | ||
node = node.add_child(ExpectCertificate()) | ||
if dhe: | ||
node = node.add_child(ExpectServerKeyExchange()) | ||
node = node.add_child(ExpectServerHelloDone()) | ||
node = node.add_child(ClientKeyExchangeGenerator()) | ||
node = node.add_child(ChangeCipherSpecGenerator()) | ||
node = node.add_child(FinishedGenerator()) | ||
node = node.add_child(ExpectChangeCipherSpec()) | ||
node = node.add_child(ExpectFinished()) | ||
# send GET request | ||
node = node.add_child(ApplicationDataGenerator( | ||
bytearray(b"GET /secure/test HTTP/1.0\r\n\r\n"))) | ||
if early_abort: | ||
node = node.add_child(ExpectAlert(AlertLevel.fatal, | ||
AlertDescription.handshake_failure)) | ||
node = node.add_child(ExpectClose()) | ||
else: | ||
node = node.add_child(ExpectHelloRequest()) | ||
# 2nd handshake | ||
node = node.add_child(ResetHandshakeHashes()) | ||
ext = {} | ||
ext[ExtensionType.signature_algorithms] = SignatureAlgorithmsExtension()\ | ||
.create(sig_algs) | ||
ext[ExtensionType.signature_algorithms_cert] = \ | ||
SignatureAlgorithmsCertExtension().create(sig_algs) | ||
node = node.add_child(ClientHelloGenerator(ciphers, | ||
session_id=bytearray(0), | ||
extensions=ext)) | ||
if no_ins_renego: | ||
node = node.add_child(ExpectAlert(AlertLevel.fatal, | ||
AlertDescription.handshake_failure)) | ||
node = node.add_child(ExpectClose()) | ||
else: | ||
node = node.add_child(ExpectServerHello()) | ||
node = node.add_child(ExpectCertificate()) | ||
if dhe: | ||
node = node.add_child(ExpectServerKeyExchange()) | ||
node = node.add_child(ExpectCertificateRequest()) | ||
node = node.add_child(ExpectServerHelloDone()) | ||
node = node.add_child(CertificateGenerator(X509CertChain([cert]))) | ||
node = node.add_child(ClientKeyExchangeGenerator()) | ||
node = node.add_child(CertificateVerifyGenerator(private_key)) | ||
node = node.add_child(ChangeCipherSpecGenerator()) | ||
node = node.add_child(FinishedGenerator()) | ||
node = node.add_child(ExpectChangeCipherSpec()) | ||
node = node.add_child(ExpectFinished()) | ||
node = node.add_child(ExpectApplicationData()) | ||
conversations["try insecure (legacy) renegotiation"] = conversation | ||
|
||
# run the conversation | ||
good = 0 | ||
bad = 0 | ||
failed = [] | ||
if not num_limit: | ||
num_limit = len(conversations) | ||
|
||
# make sure that sanity test is run first and last | ||
# to verify that server was running and kept running throughout | ||
sanity_tests = [('sanity', conversations['sanity'])] | ||
regular_tests = [(k, v) for k, v in conversations.items() if k != 'sanity'] | ||
sampled_tests = sample(regular_tests, min(num_limit, len(regular_tests))) | ||
ordered_tests = chain(sanity_tests, sampled_tests, sanity_tests) | ||
|
||
for c_name, c_test in ordered_tests: | ||
if run_only and c_name not in run_only or c_name in run_exclude: | ||
continue | ||
print("{0} ...".format(c_name)) | ||
|
||
runner = Runner(c_test) | ||
|
||
res = True | ||
try: | ||
runner.run() | ||
except Exception: | ||
print("Error while processing") | ||
print(traceback.format_exc()) | ||
res = False | ||
|
||
if res: | ||
good += 1 | ||
print("OK\n") | ||
else: | ||
bad += 1 | ||
failed.append(c_name) | ||
|
||
print("Verify that the server disabled renegotiation (both legacy") | ||
print("and secure). Use client certificates for the test.\n") | ||
print("Test expects the server to ask for renegotiation after the client") | ||
print("asks for \"/secure/test\" resource\n") | ||
print("version: {0}\n".format(version)) | ||
|
||
print("Test end") | ||
print("successful: {0}".format(good)) | ||
print("failed: {0}".format(bad)) | ||
failed_sorted = sorted(failed, key=natural_sort_keys) | ||
print(" {0}".format('\n '.join(repr(i) for i in failed_sorted))) | ||
|
||
if bad > 0: | ||
sys.exit(1) | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.