summaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rwxr-xr-xt/00_testssl_help.t63
-rwxr-xr-xt/01_testssl_banner.t49
-rwxr-xr-xt/02_clientsim_txt_parsable.t27
-rwxr-xr-xt/05_ca_hashes_up_to_date.t16
-rwxr-xr-xt/10_baseline_ipv4_http.t73
-rwxr-xr-xt/11_baseline_ipv6_http.t.DISABLED61
-rwxr-xr-xt/21_baseline_starttls.t193
-rwxr-xr-xt/23_client_simulation.t79
-rwxr-xr-xt/31_isJSON_valid.t88
-rwxr-xr-xt/32_isHTML_valid.t92
-rwxr-xr-xt/33_isJSON_severitylevel_valid.t69
-rwxr-xr-xt/51_badssl.com.t147
-rwxr-xr-xt/59_hpkp.t.tmpDISABLED84
-rwxr-xr-xt/61_diff_testsslsh.t70
-rw-r--r--t/Readme.md10
-rw-r--r--t/baseline_data/default_testssl.csvfile140
16 files changed, 1261 insertions, 0 deletions
diff --git a/t/00_testssl_help.t b/t/00_testssl_help.t
new file mode 100755
index 0000000..bedcb8c
--- /dev/null
+++ b/t/00_testssl_help.t
@@ -0,0 +1,63 @@
+#!/usr/bin/env perl
+
+# Basics: is there a syntax error where already bash hiccups on?
+
+use strict;
+use Test::More;
+use File::stat;
+
+my $tests = 0;
+my $fileout="";
+my $prg="./testssl.sh";
+my $out="";
+
+# Try to detect remainders from debugging:
+my $debug_regexp='^(\s)*set (-|\+)x';
+# Blacklists we use to trigger an error:
+my $error_regexp1='(syntax|parse) (e|E)rror';
+my $error_regexp2='testssl.sh: line';
+my $error_regexp3='bash: warning';
+my $error_regexp4='command not found';
+my $error_regexp5='(syntax error|unexpected token)';
+
+printf "\n%s\n", "Testing whether just calling \"./testssl.sh\" produces no error ...";
+my $info = stat($prg);
+my $retMode = $info->mode;
+
+is($retMode & 0400, 0400, "Checking \"./testssl.sh\" for read permission");
+$tests++;
+
+is($retMode & 0100, 0100, "Checking \"./testssl.sh\" for execute permission");
+$tests++;
+
+$fileout = `timeout 10 bash $prg 2>&1`;
+my $retval=$?;
+
+unlike($fileout, qr/$error_regexp1/, "regex 1");
+$tests++;
+
+unlike($fileout, qr/$error_regexp2/, "regex 2");
+$tests++;
+
+unlike($fileout, qr/$error_regexp3/, "regex 3");
+$tests++;
+
+unlike($fileout, qr/$error_regexp4/, "regex 4");
+$tests++;
+
+unlike($fileout, qr/$error_regexp5/, "regex 5");
+$tests++;
+
+is($retval, 0, "return value should be equal zero: \"$retval\"");
+$tests++;
+
+$out=`grep -E "$debug_regexp" $prg`;
+unlike($out, qr/$debug_regexp/, "Debug RegEx");
+$tests++;
+
+printf "\n";
+done_testing($tests);
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/01_testssl_banner.t b/t/01_testssl_banner.t
new file mode 100755
index 0000000..032077a
--- /dev/null
+++ b/t/01_testssl_banner.t
@@ -0,0 +1,49 @@
+#!/usr/bin/env perl
+
+# Basics: is there a syntax error where already bash hiccups on?
+# --banner is equal to --version
+
+use strict;
+use Test::More;
+
+my $tests = 0;
+my $fileout="";
+# Blacklists we use to trigger an error:
+my $error_regexp1='(syntax|parse) (e|E)rror';
+my $error_regexp2='testssl.sh: line';
+my $error_regexp3='bash: warning';
+my $error_regexp4='command not found';
+my $error_regexp5='(syntax error|unexpected token)';
+my $good_regexp='free software([\s\S]*)USAGE w/o ANY WARRANTY([\s\S]*)OWN RISK([\s\S]*)Using([\s\S]*)ciphers([\s\S]*)built([\s\S]*)platform';
+
+printf "\n%s\n", "Testing whether just calling \"./testssl.sh --banner\" produces no error ...";
+$fileout = `timeout 10 bash ./testssl.sh --banner 2>&1`;
+my $retval=$?;
+
+unlike($fileout, qr/$error_regexp1/, "regex 1");
+$tests++;
+
+unlike($fileout, qr/$error_regexp2/, "regex 2");
+$tests++;
+
+unlike($fileout, qr/$error_regexp3/, "regex 3");
+$tests++;
+
+unlike($fileout, qr/$error_regexp4/, "regex 4");
+$tests++;
+
+unlike($fileout, qr/$error_regexp5/, "regex 5");
+$tests++;
+
+like($fileout, qr/$good_regexp/, "regex positive");
+$tests++;
+
+is($retval, 0, "return value should be equal zero: \"$retval\"");
+$tests++;
+
+printf "\n";
+done_testing($tests);
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/02_clientsim_txt_parsable.t b/t/02_clientsim_txt_parsable.t
new file mode 100755
index 0000000..4cbfd01
--- /dev/null
+++ b/t/02_clientsim_txt_parsable.t
@@ -0,0 +1,27 @@
+#!/usr/bin/env perl
+
+# Just a functional test, whether ~/etc/client-simulation.txt
+# doesn't have any syntax errors
+
+use strict;
+use Test::More;
+
+my $tests = 0;
+my $fileout="";
+# Blacklists we use to trigger an error:
+my $error_regexp1='(syntax|parse) (e|E)rror';
+my $error_regexp2='client-simulation.txt:';
+
+printf "\n%s\n", "Testing whether \"~/etc/client-simulation.txt\" isn't broken ...";
+$fileout = `bash ./etc/client-simulation.txt 2>&1`;
+unlike($fileout, qr/$error_regexp1/, "regex 1");
+$tests++;
+
+unlike($fileout, qr/$error_regexp2/, "regex 2");
+$tests++;
+
+printf "\n";
+done_testing($tests);
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/05_ca_hashes_up_to_date.t b/t/05_ca_hashes_up_to_date.t
new file mode 100755
index 0000000..434b7a0
--- /dev/null
+++ b/t/05_ca_hashes_up_to_date.t
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+
+printf "\n%s\n", "Testing whether CA certificates are newer their SPKI hashes \"~/etc/ca_hashes.txt\" ...";
+
+my $newer_bundles=`find etc/*.pem -newer etc/ca_hashes.txt`;
+is($newer_bundles,"","If there's an output with a *.pem file run \"~/utils/create_ca_hashes.sh\"");
+
+printf "\n";
+done_testing;
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/10_baseline_ipv4_http.t b/t/10_baseline_ipv4_http.t
new file mode 100755
index 0000000..c98e6f2
--- /dev/null
+++ b/t/10_baseline_ipv4_http.t
@@ -0,0 +1,73 @@
+#!/usr/bin/env perl
+
+# baseline test for testssl, screen and JSON output
+
+# This is referred by the documentation.
+
+# We could also inspect the JSON for any problems for
+# "id" : "scanProblem"
+# "finding" : "Scan interrupted"
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use JSON;
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $check2run="-p -s -P --fs -S -h -U -q --ip=one --color 0";
+my $uri="google.com";
+my $socket_out="";
+my $openssl_out="";
+# Blacklists we use to trigger an error:
+my $socket_regex_bl='(e|E)rror|\.\/testssl\.sh: line |(f|F)atal|(c|C)ommand not found';
+my $openssl_regex_bl='(e|E)rror|(f|F)atal|\.\/testssl\.sh: line |Oops|s_client connect problem|(c|C)ommand not found';
+my $json_regex_bl='(id".*:\s"scanProblem"|severity".*:\s"FATAL"|"Scan interrupted")';
+
+my $socket_json="";
+my $openssl_json="";
+$check2run="--jsonfile tmp.json $check2run";
+
+die "Unable to open $prg" unless -f $prg;
+
+# Provide proper start conditions
+unlink "tmp.json";
+
+# Title
+printf "\n%s\n", "Baseline unit test IPv4 against \"$uri\"";
+
+#1
+$socket_out = `$prg $check2run $uri 2>&1`;
+$socket_json = json('tmp.json');
+unlink "tmp.json";
+unlike($socket_out, qr/$socket_regex_bl/, "via sockets, terminal output");
+$tests++;
+unlike($socket_json, qr/$json_regex_bl/, "via sockets JSON output");
+$tests++;
+
+#2
+$openssl_out = `$prg --ssl-native $check2run $uri 2>&1`;
+$openssl_json = json('tmp.json');
+unlink "tmp.json";
+# With Google only we sometimes encounter an error as they return a 0 char with openssl, so we white list this pattern here:
+# It should be fixed in the code though so we comment this out
+# $openssl_out =~ s/testssl.*warning: command substitution: ignored null byte in input\n//g;
+unlike($openssl_out, qr/$openssl_regex_bl/, "via OpenSSL");
+$tests++;
+unlike($openssl_json, qr/$json_regex_bl/, "via OpenSSL JSON output");
+$tests++;
+
+done_testing($tests);
+printf "\n";
+
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/11_baseline_ipv6_http.t.DISABLED b/t/11_baseline_ipv6_http.t.DISABLED
new file mode 100755
index 0000000..affa18a
--- /dev/null
+++ b/t/11_baseline_ipv6_http.t.DISABLED
@@ -0,0 +1,61 @@
+#!/usr/bin/env perl
+
+# disabled as IPv6 is not supported by Travis, see https://github.com/drwetter/testssl.sh/issues/1177
+
+# Just a functional test, whether there are any problems on the client side
+# Probably we could also inspect the JSON for any problems for
+# "id" : "scanProblem"
+# "finding" : "Scan interrupted"
+
+use strict;
+use Test::More;
+use Data::Dumper;
+# use JSON;
+# if we need JSON we need to comment this and the lines below in
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $check2run ="-p -s -P --fs -S -h -U -q --ip=one --color 0";
+my $uri="";
+my $socket_out="";
+my $openssl_out="";
+# Blacklists we use to trigger an error:
+my $socket_regex_bl='(e|E)rror|\.\/testssl\.sh: line |(f|F)atal|(c|C)ommand not found';
+my $openssl_regex_bl='(e|E)rror|(f|F)atal|\.\/testssl\.sh: line |Oops|s_client connect problem|(c|C)ommand not found';
+
+# my $socket_json="";
+# my $openssl_json="";
+# $check2run="--jsonfile tmp.json $check2run";
+
+die "Unable to open $prg" unless -f $prg;
+
+$uri="testssl.net";
+
+# unlink "tmp.json";
+printf "\n%s\n", "Baseline unit test IPv6 via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -6 $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+# unlink "tmp.json";
+printf "\n%s\n", "Baseline unit test IPv6 via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh --ssl-native $check2run -6 $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+# With Google only we encounter an error as they return a 0 char with openssl, so we white list this pattern here:
+$openssl_out =~ s/testssl.*warning: command substitution: ignored null byte in input\n//g;
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+done_testing($tests);
+unlink "tmp.json";
+
+
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
diff --git a/t/21_baseline_starttls.t b/t/21_baseline_starttls.t
new file mode 100755
index 0000000..8778b98
--- /dev/null
+++ b/t/21_baseline_starttls.t
@@ -0,0 +1,193 @@
+#!/usr/bin/env perl
+
+# Just a functional test, whether there are any problems on the client side
+# Probably we could also inspect the JSON for any problems for
+# "id" : "scanProblem"
+# "finding" : "Scan interrupted"
+
+# Catches:
+# - This unit test takes very long
+# - Hosts which match the regex patterns should be avoided
+
+use strict;
+use Test::More;
+use Data::Dumper;
+# use JSON;
+# if we need JSON we need to comment this and the lines below in
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $check2run_smtp="--protocols --standard --fs --server-preference --headers --vulnerable -q --ip=one --color 0";
+my $check2run="-q --ip=one --color 0";
+my $uri="";
+my $socket_out="";
+my $openssl_out="";
+# Blacklists we use to trigger an error:
+my $socket_regex_bl='(e|E)rror|\.\/testssl\.sh: line |(f|F)atal|(c|C)ommand not found';
+my $openssl_regex_bl='(e|E)rror|(f|F)atal|\.\/testssl\.sh: line |Oops|s_client connect problem|(c|C)ommand not found';
+
+# my $socket_json="";
+# my $openssl_json="";
+# $check2run_smtp="--jsonfile tmp.json $check2run_smtp";
+# $check2run="--jsonfile tmp.json $check2run";
+
+die "Unable to open $prg" unless -f $prg;
+
+$uri="smtp-relay.gmail.com:587";
+
+# we will have client simulations later, so we don't need to run everything again:
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS SMTP unit test via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run_smtp -t smtp $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS SMTP unit tests via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh --ssl-native $check2run_smtp -t smtp $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+$uri="pop.gmx.net:110";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS POP3 unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t pop3 $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+printf "\n%s\n", "STARTTLS POP3 unit tests via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh --ssl-native $check2run -t pop3 $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+$uri="imap.gmx.net:143";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS IMAP unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t imap $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+printf "\n%s\n", "STARTTLS IMAP unit tests via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh --ssl-native $check2run -t imap $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+$uri="mail.tigertech.net:4190";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS MANAGE(SIEVE) unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t sieve $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+$uri="jabber.org:5222";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS XMPP unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t xmpp $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+# commented out, bc of travis' limits
+#
+#printf "\n%s\n", "STARTTLS XMPP unit tests via OpenSSL --> $uri ...";
+#$openssl_out = `./testssl.sh --ssl-native $check2run -t xmpp $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+#unlike($openssl_out, qr/$openssl_regex_bl/, "");
+#$tests++;
+
+# $uri="jabber.ccc.de:5269";
+# printf "\n%s\n", "Quick STARTTLS XMPP S2S unit tests via sockets --> $uri ...";
+# $openssl_out = `./testssl.sh --openssl=/usr/bin/openssl -p $check2run -t xmpp-server $uri 2>&1`;
+# # $openssl_json = json('tmp.json');
+# unlike($openssl_out, qr/$openssl_regex_bl/, "");
+# $tests++;
+
+
+$uri="ldap.uni-rostock.de:21";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS FTP unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t ftp $uri 2>&1`;
+# $socket_json = json('tmp.json');
+# OCSP stapling fails sometimes with: 'offered, error querying OCSP responder (ERROR: No Status found)'
+$socket_out =~ s/ error querying OCSP responder .*\n//g;
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+# commented out, bc of travis' limits
+#
+# printf "\n%s\n", "STARTTLS FTP unit tests via OpenSSL --> $uri ...";
+# $openssl_out = `./testssl.sh --ssl-native $check2run -t ftp $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+# OCSP stapling fails sometimes with: 'offered, error querying OCSP responder (ERROR: No Status found)'
+# $openssl_out =~ s/ error querying OCSP responder .*\n//g;
+# unlike($openssl_out, qr/$openssl_regex_bl/, "");
+# $tests++;
+
+
+# https://ldapwiki.com/wiki/Public%20LDAP%20Servers
+$uri="db.debian.org:389";
+
+printf "\n%s\n", "STARTTLS LDAP unit tests via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t ldap $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+printf "\n%s\n", "STARTTLS LDAP unit tests via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh --ssl-native $check2run -t ldap $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+# For NNTP there doesn't seem to be reliable host out there
+#$uri="144.76.182.167:119";
+
+#printf "\n%s\n", "STARTTLS NNTP unit tests via sockets --> $uri ...";
+#$socket_out = `./testssl.sh $check2run -t nntp $uri 2>&1`;
+#unlike($socket_out, qr/$socket_regex_bl/, "");
+#$tests++;
+
+# commented out, bc of travis' limits
+#
+#printf "\n%s\n", "STARTTLS NNTP unit tests via OpenSSL --> $uri ...";
+#$openssl_out = `./testssl.sh --ssl-native $check2run -t nntp $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+#unlike($openssl_out, qr/$openssl_regex_bl/, "");
+#$tests++;
+
+
+# IRC: missing
+# LTMP, mysql, postgres
+
+
+
+done_testing($tests);
+# unlink "tmp.json";
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/23_client_simulation.t b/t/23_client_simulation.t
new file mode 100755
index 0000000..dc310a7
--- /dev/null
+++ b/t/23_client_simulation.t
@@ -0,0 +1,79 @@
+#!/usr/bin/env perl
+
+# Just a functional test, whether there are any problems on the client side
+# Probably we could also inspect the JSON for any problems for
+# "id" : "scanProblem"
+# "finding" : "Scan interrupted"
+
+use strict;
+use Test::More;
+use Data::Dumper;
+# use JSON;
+# if we need JSON we need to comment this and the lines below in
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $check2run ="--client-simulation -q --ip=one --color 0";
+my $uri="";
+my $socket_out="";
+my $openssl_out="";
+# Blacklists we use to trigger an error:
+my $socket_regex_bl='(e|E)rror|\.\/testssl\.sh: line |(f|F)atal|(c|C)ommand not found';
+my $openssl_regex_bl='(e|E)rror|(f|F)atal|\.\/testssl\.sh: line |Oops|s_client connect problem|(c|C)ommand not found';
+
+# my $socket_json="";
+# my $openssl_json="";
+# $check2run="--jsonfile tmp.json $check2run";
+
+die "Unable to open $prg" unless -f $prg;
+
+$uri="google.com";
+
+# unlink "tmp.json";
+printf "\n%s\n", "Client simulations unit test via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+# unlink "tmp.json";
+printf "\n%s\n", "Client simulations unit test via OpenSSL --> $uri ...";
+$openssl_out = `./testssl.sh $check2run --ssl-native $uri 2>&1`;
+# $openssl_json = json('tmp.json');
+unlike($openssl_out, qr/$openssl_regex_bl/, "");
+$tests++;
+
+
+$uri="smtp-relay.gmail.com:587";
+
+# unlink "tmp.json";
+printf "\n%s\n", "STARTTLS: Client simulations unit test via sockets --> $uri ...";
+$socket_out = `./testssl.sh $check2run -t smtp $uri 2>&1`;
+# $socket_json = json('tmp.json');
+unlike($socket_out, qr/$socket_regex_bl/, "");
+$tests++;
+
+# commented out, bc of travis' limits
+#
+# unlink "tmp.json";
+#printf "\n%s\n", "STARTTLS: Client simulations unit test via OpenSSL --> $uri ...";
+#$openssl_out = `./testssl.sh --ssl-native $check2run -t smtp $uri 2>&1`;
+## $openssl_json = json('tmp.json');
+#unlike($openssl_out, qr/$openssl_regex_bl/, "");
+#$tests++;
+
+done_testing($tests);
+unlink "tmp.json";
+
+
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/31_isJSON_valid.t b/t/31_isJSON_valid.t
new file mode 100755
index 0000000..2637ac1
--- /dev/null
+++ b/t/31_isJSON_valid.t
@@ -0,0 +1,88 @@
+#!/usr/bin/env perl
+
+# This is more a PoC. Improvements welcome!
+#
+
+use strict;
+use Test::More;
+use JSON;
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $check2run ="--ip=one --ids-friendly -q --color 0";
+my $uri="";
+my $json="";
+my $out="";
+# Blacklists we use to trigger an error:
+my $socket_regex_bl='(e|E)rror|\.\/testssl\.sh: line |(f|F)atal|(c|C)ommand not found';
+my $openssl_regex_bl='(e|E)rror|(f|F)atal|\.\/testssl\.sh: line |Oops|s_client connect problem|(c|C)ommand not found';
+
+die "Unable to open $prg" unless -f $prg;
+
+my $uri="cloudflare.com";
+
+printf "\n%s\n", "Unit testing JSON output ...";
+unlink 'tmp.json';
+
+#1
+printf "%s\n", ".. plain JSON --> $uri ";
+$out = `./testssl.sh $check2run --jsonfile tmp.json $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+my @errors=eval { decode_json($json) };
+is(@errors,0,"no errors");
+$tests++;
+
+#2
+printf "%s\n", ".. pretty JSON --> $uri ";
+$out = `./testssl.sh $check2run --jsonfile-pretty tmp.json $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+@errors=eval { decode_json($json) };
+is(@errors,0,"no errors");
+$tests++;
+
+
+#3
+# This testssl.sh run deliberately does NOT work as travis-ci.org blocks port 25 egress.
+# but the output should be fine. The idea is to have a unit test for a failed connection.
+printf "%s\n", ".. plain JSON for a failed run: '--mx $uri' ...";
+$out = `./testssl.sh --ssl-native --openssl-timeout=10 $check2run --jsonfile tmp.json --mx $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+@errors=eval { decode_json($json) };
+is(@errors,0,"no errors");
+$tests++;
+
+#4
+# Same as above but with pretty JSON
+printf "%s\n", ".. pretty JSON for a failed run '--mx $uri' ...";
+$out = `./testssl.sh --ssl-native --openssl-timeout=10 $check2run --jsonfile-pretty tmp.json --mx $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+@errors=eval { decode_json($json) };
+is(@errors,0,"no errors");
+$tests++;
+
+#5
+my $uri = "smtp-relay.gmail.com:587";
+printf "%s\n", " .. plain JSON and STARTTLS --> $uri ...";
+$out = `./testssl.sh --jsonfile tmp.json $check2run -t smtp $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+@errors=eval { decode_json($json) };
+is(@errors,0,"no errors");
+$tests++;
+
+printf "\n";
+done_testing($tests);
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/32_isHTML_valid.t b/t/32_isHTML_valid.t
new file mode 100755
index 0000000..c006329
--- /dev/null
+++ b/t/32_isHTML_valid.t
@@ -0,0 +1,92 @@
+#!/usr/bin/env perl
+
+# Checking whether the HTML output is somehow valid
+# This could be amended by using HTML::Tidy or HTML::Valid
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use Text::Diff;
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $uri="heise.de";
+my $out="";
+my $html="";
+my $debughtml="";
+my $edited_html="";
+my $htmlfile="tmp.html";
+my $check2run="--ip=one --sneaky --ids-friendly --color 0 --htmlfile $htmlfile";
+my $diff="";
+die "Unable to open $prg" unless -f $prg;
+
+printf "\n%s\n", "Doing HTML output checks";
+unlink $htmlfile;
+
+#1
+printf "%s\n", " .. running $prg against \"$uri\" to create HTML and terminal outputs (may take ~2 minutes)";
+# specify a TERM_WIDTH so that the two calls to testssl.sh don't create HTML files with different values of TERM_WIDTH
+$out = `TERM_WIDTH=120 $prg $check2run $uri`;
+$html = `cat $htmlfile`;
+# $edited_html will contain the HTML with formatting information removed in order to compare against terminal output
+# Start by removing the HTML header.
+$edited_html = `tail -n +11 $htmlfile`;
+unlink $htmlfile;
+
+# Remove the HTML footer
+$edited_html =~ s/\n\<\/pre\>\n\<\/body\>\n\<\/html\>//;
+# Remove any hypertext links for URLs
+$edited_html =~ s/<a href=[0-9A-Za-z ";:_&=\/\.\?\-]*>//g;
+$edited_html =~ s/<\/a>//g;
+
+# Replace escaped characters with their original text
+$edited_html =~ s/&amp;/&/g;
+$edited_html =~ s/&lt;/</g;
+$edited_html =~ s/&gt;/>/g;
+$edited_html =~ s/&quot;/"/g;
+$edited_html =~ s/&apos;/'/g;
+
+cmp_ok($edited_html, "eq", $out, "HTML file matches terminal output");
+$tests++;
+
+$diff = diff \$edited_html, \$out;
+printf "\n%s\n", "$diff";
+
+
+#2
+printf "\n%s\n", " .. running again $prg against \"$uri\", now with --debug 4 to create HTML output (may take another ~2 minutes)";
+# Redirect stderr to /dev/null in order to avoid some unexplained "date: invalid date" error messages
+$out = `TERM_WIDTH=120 $prg $check2run --debug 4 $uri 2> /dev/null`;
+$debughtml = `cat $htmlfile`;
+unlink $htmlfile;
+
+# Remove date information from the Start and Done banners in the two HTML files, since they were created at different times
+$html =~ s/Start 2[0-9][0-9][0-9]-[0-3][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/Start XXXX-XX-XX XX:XX:XX/;
+$debughtml =~ s/Start 2[0-9][0-9][0-9]-[0-3][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/Start XXXX-XX-XX XX:XX:XX/;
+
+$html =~ s/Done 2[0-9][0-9][0-9]-[0-3][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \[ *[0-9]*s\]/Done XXXX-XX-XX XX:XX:XX [ Xs]/;
+$debughtml =~ s/Done 2[0-9][0-9][0-9]-[0-3][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \[ *[0-9]*s\]/Done XXXX-XX-XX XX:XX:XX [ Xs]/;
+
+# Remove time difference from "HTTP clock skew" line
+$html =~ s/HTTP clock skew \+?-?[0-9]* /HTTP clock skew X /;
+$debughtml =~ s/HTTP clock skew \+?-?[0-9]* /HTTP clock skew X /;
+
+$debughtml =~ s/ Pre-test: .*\n//g;
+$debughtml =~ s/.*OK: below 825 days.*\n//g;
+$debughtml =~ s/.*DEBUG:.*\n//g;
+$debughtml =~ s/No engine or GOST support via engine with your.*\n//g;
+
+cmp_ok($debughtml, "eq", $html, "HTML file created with --debug 4 matches HTML file created without --debug");
+$tests++;
+
+$diff = diff \$debughtml, \$html;
+printf "\n%s\n", "$diff";
+
+
+
+printf "\n";
+done_testing($tests);
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/33_isJSON_severitylevel_valid.t b/t/33_isJSON_severitylevel_valid.t
new file mode 100755
index 0000000..00b04a4
--- /dev/null
+++ b/t/33_isJSON_severitylevel_valid.t
@@ -0,0 +1,69 @@
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use JSON;
+
+my (
+ $out,
+ $json,
+ $json_pretty,
+ $found,
+ $tests
+);
+
+$tests = 0;
+
+my $prg="./testssl.sh";
+my $check2run = '-S -e --ids-friendly -U --severity LOW --color 0';
+my $uri = 'badssl.com';
+
+printf "\n%s\n", "Doing severity level checks";
+
+die "Unable to open $prg" unless -f $prg;
+unlink 'tmp.json';
+
+#1
+pass(" .. running testssl.sh against $uri to create a JSON report with severity level >= LOW (may take 2~3 minutes)"); $tests++;
+$out = `$prg $check2run --jsonfile tmp.json $uri`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+$found = 0;
+cmp_ok(@$json,'>',0,"At least 1 finding is expected"); $tests++;
+foreach my $f ( @$json ) {
+ if ( $f->{severity} eq "INFO" ) {
+ $found = 1;
+ last;
+ }
+}
+is($found,0,"We should not have any finding with INFO level"); $tests++;
+
+#2
+pass(" .. running testssl.sh against $uri to create a JSON-PRETTY report with severity level >= LOW (may take 2~3 minutes)"); $tests++;
+$out = `$prg $check2run --jsonfile-pretty tmp.json $uri`;
+$json_pretty = json('tmp.json');
+unlink 'tmp.json';
+$found = 0;
+my $vulnerabilities = $json_pretty->{scanResult}->[0]->{vulnerabilities};
+foreach my $f ( @$vulnerabilities ) {
+ if ( $f->{severity} eq "INFO" ) {
+ $found = 1;
+ last;
+ }
+}
+is($found,0,"We should not have any finding with INFO level"); $tests++;
+
+printf "\n";
+done_testing($tests);
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/51_badssl.com.t b/t/51_badssl.com.t
new file mode 100755
index 0000000..40b6e91
--- /dev/null
+++ b/t/51_badssl.com.t
@@ -0,0 +1,147 @@
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use JSON;
+
+my $tests = 0;
+
+my (
+ $out,
+ $json,
+ $found,
+);
+# OK
+pass("Running testssl.sh against badssl.com to create a baseline (may take 2~3 minutes)"); $tests++;
+my $okout = `./testssl.sh -S -e --freak --logjam --drown --rc4 --sweet32 --breach --winshock --crime --jsonfile tmp.json --color 0 badssl.com`;
+my $okjson = json('tmp.json');
+unlink 'tmp.json';
+cmp_ok(@$okjson,'>',10,"We have more then 10 findings"); $tests++;
+
+# Expiration
+pass("Running testssl against expired.badssl.com"); $tests++;
+$out = `./testssl.sh -S --jsonfile tmp.json --color 0 expired.badssl.com`;
+like($out, qr/Chain of trust\s+NOT ok \(expired\)/,"The chain of trust should be expired"); $tests++;
+like($out, qr/Certificate Validity \(UTC\)\s+expired/,"The certificate should be expired"); $tests++;
+$json = json('tmp.json');
+unlink 'tmp.json';
+$found = 0;
+foreach my $f ( @$json ) {
+ if ( $f->{id} eq "cert_expirationStatus" ) {
+ $found = 1;
+ like($f->{finding},qr/^expired/,"Finding reads expired."); $tests++;
+ is($f->{severity}, "CRITICAL", "Severity should be CRITICAL"); $tests++;
+ last;
+ }
+}
+is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+# Self signed and not-expired
+pass("Running testssl against self-signed.badssl.com"); $tests++;
+$out = `./testssl.sh -S --jsonfile tmp.json --color 0 self-signed.badssl.com`;
+unlike($out, qr/Certificate Validity \(UTC\)s+expired/,"The certificate should not be expired"); $tests++;
+$json = json('tmp.json');
+unlink 'tmp.json';
+$found = 0;
+foreach my $f ( @$json ) {
+ if ( $f->{id} eq "cert_expirationStatus" ) {
+ $found = 1;
+ like($f->{finding},qr/days/,"Finding doesn't read expired."); $tests++;
+ isnt($f->{severity}, "CRITICAL", "Severity should be OK, MEDIUM or HIGH"); $tests++;
+ last;
+ }
+}
+is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+like($out, qr/Chain of trust.*?NOT ok.*\(self signed\)/,"Chain of trust should fail because of self signed"); $tests++;
+$found = 0;
+foreach my $f ( @$json ) {
+ if ( $f->{id} eq "cert_chain_of_trust" ) {
+ $found = 1;
+ like($f->{finding},qr/^.*self signed/,"Finding says certificate cannot be trusted."); $tests++;
+ is($f->{severity}, "CRITICAL", "Severity should be CRITICAL"); $tests++;
+ last;
+ }
+}
+is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+like($okout, qr/Chain of trust[^\n]*?Ok/,"Chain of trust should be ok"); $tests++;
+$found = 0;
+foreach my $f ( @$okjson ) {
+ if ( $f->{id} eq "cert_chain_of_trust" ) {
+ $found = 1;
+ like($f->{finding},qr/passed/,"Finding says certificate can be trusted."); $tests++;
+ # is($f->{finding},"^.*passed.*","Finding says certificate can be trusted."); $tests++;
+ is($f->{severity}, "OK", "Severity should be OK"); $tests++;
+ last;
+ }
+}
+is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+# Wrong host
+#pass("Running testssl against wrong.host.badssl.com"); $tests++;
+#$out = `./testssl.sh -S --jsonfile tmp.json --color 0 wrong.host.badssl.com`;
+#unlike($out, qr/Certificate Expiration\s+expired\!/,"The certificate should not be expired"); $tests++;
+#$json = json('tmp.json');
+#unlink 'tmp.json';
+#$found = 0;
+#foreach my $f ( @$json ) {
+# if ( $f->{id} eq "expiration" ) {
+# $found = 1;
+# unlike($f->{finding},qr/^Certificate Expiration.*expired\!/,"Finding should not read expired."); $tests++;
+# is($f->{severity}, "ok", "Severity should be ok"); $tests++;
+# last;
+# }
+#}
+#is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+# Incomplete chain
+pass("Running testssl against incomplete-chain.badssl.com"); $tests++;
+$out = `./testssl.sh -S --jsonfile tmp.json --color 0 incomplete-chain.badssl.com`;
+like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
+$json = json('tmp.json');
+unlink 'tmp.json';
+$found = 0;
+foreach my $f ( @$json ) {
+ if ( $f->{id} eq "cert_chain_of_trust" ) {
+ $found = 1;
+ like($f->{finding},qr/^.*chain incomplete/,"Finding says certificate cannot be trusted."); $tests++;
+ is($f->{severity}, "CRITICAL", "Severity should be CRITICAL"); $tests++;
+ last;
+ }
+}
+is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+# TODO: RSA 8192
+
+# TODO: CBC
+#pass("Running testssl against cbc.badssl.com"); $tests++;
+#$out = `./testssl.sh -e -U --jsonfile tmp.json --color 0 cbc.badssl.com`;
+#like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
+#$json = json('tmp.json');
+#unlink 'tmp.json';
+#$found = 0;
+#foreach my $f ( @$json ) {
+# if ( $f->{id} eq "cert_chain_of_trust" ) {
+# $found = 1;
+# like($f->{finding},qr/^All certificate trust checks failed.*incomplete/,"Finding says certificate cannot be trusted."); $tests++;
+# is($f->{severity}, "CRITICAL", "Severity should be CRITICAL"); $tests++;
+# last;
+# }
+#}
+#is($found,1,"We had a finding for this in the JSON output"); $tests++;
+
+
+done_testing($tests);
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/59_hpkp.t.tmpDISABLED b/t/59_hpkp.t.tmpDISABLED
new file mode 100755
index 0000000..94dcea5
--- /dev/null
+++ b/t/59_hpkp.t.tmpDISABLED
@@ -0,0 +1,84 @@
+#!/usr/bin/env perl
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use JSON;
+
+my $tests = 0;
+
+my (
+ $out,
+ $json,
+ $found,
+);
+# OK
+pass("Running testssl.sh against ssl.sectionzero.org"); $tests++;
+$out = `./testssl.sh --headers --jsonfile tmp.json --color 0 ssl.sectionzero.org`;
+$json = json('tmp.json');
+unlink 'tmp.json';
+
+# It is better to have findings in a hash
+# Look for a host cert match in the process.
+my $found = 0;
+my %findings;
+foreach my $f ( @$json ) {
+ $findings{$f->{id}} = $f;
+ if ( $f->{finding} =~ /matches the host certificate/ ) {
+ $found++;
+ }
+}
+is($found,1,"We found 1 'matches the host certificate' finding"); $tests++;
+like($out,'/Host cert/',"There is a 'host cert match' in the text output"); $tests++;
+
+# Sub CA match
+ok( exists $findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"},"We have a finding for SPKI YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"); $tests++;
+like($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{finding},'/matches Intermediate CA \'Let\'s Encrypt Authority X3\' pinned in the HPKP header/',"We have our Sub CA finding"); $tests++;
+is($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{severity}, "OK", "The finding is ok"); $tests++;
+like($out,'/Sub CA\: YLh1dUR9y6Kja30RrAn7JKnbQG\/uEtLMkBgFF2Fuihg/',"There is a 'Sub CA match' in the text output"); $tests++;
+
+# Root CA match Lets encrypt
+ok( exists $findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"},"We have a finding for SPKI Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"); $tests++;
+like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/matches Root CA \'DST Root CA X3\' pinned in the HPKP header/',"This is a Root CA finding"); $tests++;
+like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/DST Root CA X3/',"Correct Root CA"); $tests++;
+like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/matches Root CA \'DST Root CA X3\' pinned in the HPKP header\. \(Root CA part of the chain\)/',"CA is indeed part of chain"); $tests++;
+is($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{severity}, "INFO", "The finding is informational"); $tests++;
+like($out,'/Root CA\: Vjs8r4z\+80wjNcr1YKepWQboSIRi63WsWXhIMN\+eWys/',"There is a 'Root CA match' in the text output"); $tests++;
+
+# Root CA StartCom
+ok( exists $findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"},"We have a finding for SPKI 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"); $tests++;
+like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/matches Root CA \'StartCom Certification Authority\' pinned in the HPKP header/',"This is a Root CA finding"); $tests++;
+like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/StartCom Certification Authority/',"Correct Root CA"); $tests++;
+like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/matches Root CA \'StartCom Certification Authority\' pinned in the HPKP header\. \(Root backup SPKI\)/',"CA is indeed NOT part of chain"); $tests++;
+is($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{severity}, "INFO", "The finding is informational"); $tests++;
+like($out,'/Backups\: 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU/',"There is a 'Root CA match' in the text output"); $tests++;
+
+# Bad PIN
+ok( exists $findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"},"We have a finding for SPKI MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"); $tests++;
+like($findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"}->{finding},'/doesn\'t match anything/',"It doesn't match indeed"); $tests++;
+is($findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"}->{severity}, "INFO", "The finding is informational"); $tests++;
+like($out,'/MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI/',"There is an 'unmatched key' in the text output"); $tests++;
+
+like($findings{hpkp_spkis}->{finding},'/5 keys pinned/',"5 keys pinned in json"); $tests++;
+like($out,'/5 keys/',"5 keys pinned in text output"); $tests++;
+
+like($findings{hpkp_age}->{finding},'/90 days/',"90 days in json"); $tests++;
+like($out,'/90 days/',"90 days in text output"); $tests++;
+
+like($findings{hpkp_subdomains}->{finding},'/this domain only/',"this domain only in json"); $tests++;
+like($out,'/just this domain/',"just this domain text output"); $tests++;
+
+like($findings{hpkp_preload}->{finding},'/NOT marked for/',"no preloading in json"); $tests++;
+
+done_testing($tests);
+
+sub json($) {
+ my $file = shift;
+ $file = `cat $file`;
+ unlink $file;
+ return from_json($file);
+}
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/61_diff_testsslsh.t b/t/61_diff_testsslsh.t
new file mode 100755
index 0000000..f4070b1
--- /dev/null
+++ b/t/61_diff_testsslsh.t
@@ -0,0 +1,70 @@
+#!/usr/bin/env perl
+
+# Baseline diff test against testssl.sh (csv output)
+#
+# We don't use a full run yet and only the certificate section.
+# There we would need to blacklist at least:
+# cert_serialNumber, cert_fingerprintSHA1, cert_fingerprintSHA256, cert
+# cert_expirationStatus, cert_notBefore, cert_notAfter, cert_caIssuers, intermediate_cert
+#
+# help is appreciated here
+
+use strict;
+use Test::More;
+use Data::Dumper;
+use Text::Diff;
+
+my $tests = 0;
+my $prg="./testssl.sh";
+my $master_socket_csv="./t/baseline_data/default_testssl.csvfile";
+my $socket_csv="tmp.csv";
+my $check2run="-p -s -P --fs -h -U -c -q --ip=one --color 0 --csvfile $socket_csv";
+#my $check2run="-p --color 0 --csvfile $socket_csv";
+my $uri="testssl.sh";
+my $diff="";
+
+die "Unable to open $prg" unless -f $prg;
+die "Unable to open $master_socket_csv" unless -f $master_socket_csv;
+
+
+# Provide proper start conditions
+unlink "tmp.csv";
+
+# Title
+printf "\n%s\n", "Diff unit test IPv4 against \"$uri\"";
+
+#1 run
+`$prg $check2run $uri 2>&1`;
+
+$diff = diff $socket_csv, $master_socket_csv;
+
+$socket_csv=`cat tmp.csv`;
+$master_socket_csv=`cat $master_socket_csv`;
+
+# Filter for changes that are allowed to occur
+$socket_csv=~ s/HTTP_clock_skew.*\n//g;
+$master_socket_csv=~ s/HTTP_clock_skew.*\n//g;
+
+# DROWN
+$socket_csv=~ s/censys.io.*\n//g;
+$master_socket_csv=~ s/censys.io.*\n//g;
+
+# HTTP time
+$socket_csv=~ s/HTTP_headerTime.*\n//g;
+$master_socket_csv=~ s/HTTP_headerTime.*\n//g;
+
+# Compare the differences to the master file -- and print differences if there were detected.
+#
+cmp_ok($socket_csv, "eq", $master_socket_csv, "Check whether CSV output matches master file from $uri") or
+ diag ("\n%s\n", "$diff");
+
+$tests++;
+
+unlink "tmp.csv";
+
+done_testing($tests);
+printf "\n";
+
+
+# vim:ts=5:sw=5:expandtab
+
diff --git a/t/Readme.md b/t/Readme.md
new file mode 100644
index 0000000..272372b
--- /dev/null
+++ b/t/Readme.md
@@ -0,0 +1,10 @@
+### Naming scheme
+
+* 00-05: Does the bare testssl.sh work at all?
+* 10-29: Do scans work fine (client side)?
+* 30-39: Does reporting work?
+* 50-69: Are the results what I expect (server side)?
+
+Please help to write Travis/CI tests! Documentation can be found [here](https://perldoc.perl.org/Test/More.html).
+You can consult the existing code here. Feel free to use `10_baseline_ipv4_http.t` or `23_client_simulation.t` as a
+template.
diff --git a/t/baseline_data/default_testssl.csvfile b/t/baseline_data/default_testssl.csvfile
new file mode 100644
index 0000000..28118ba
--- /dev/null
+++ b/t/baseline_data/default_testssl.csvfile
@@ -0,0 +1,140 @@
+"id","fqdn/ip","port","severity","finding","cve","cwe"
+"service","testssl.sh/81.169.166.184","443","INFO","HTTP","",""
+"pre_128cipher","testssl.sh/81.169.166.184","443","INFO","No 128 cipher limit bug","",""
+"SSLv2","testssl.sh/81.169.166.184","443","OK","not offered","",""
+"SSLv3","testssl.sh/81.169.166.184","443","OK","not offered","",""
+"TLS1","testssl.sh/81.169.166.184","443","LOW","offered (deprecated)","",""
+"TLS1_1","testssl.sh/81.169.166.184","443","LOW","offered (deprecated)","",""
+"TLS1_2","testssl.sh/81.169.166.184","443","OK","offered","",""
+"TLS1_3","testssl.sh/81.169.166.184","443","OK","offered with final","",""
+"NPN","testssl.sh/81.169.166.184","443","INFO","offered with h2, http/1.1 (advertised)","",""
+"ALPN_HTTP2","testssl.sh/81.169.166.184","443","OK","h2","",""
+"ALPN","testssl.sh/81.169.166.184","443","INFO","http/1.1","",""
+"cipherlist_NULL","testssl.sh/81.169.166.184","443","OK","not offered","","CWE-327"
+"cipherlist_aNULL","testssl.sh/81.169.166.184","443","OK","not offered","","CWE-327"
+"cipherlist_EXPORT","testssl.sh/81.169.166.184","443","OK","not offered","","CWE-327"
+"cipherlist_LOW","testssl.sh/81.169.166.184","443","OK","not offered","","CWE-327"
+"cipherlist_3DES_IDEA","testssl.sh/81.169.166.184","443","INFO","not offered","","CWE-310"
+"cipherlist_OBSOLETED","testssl.sh/81.169.166.184","443","LOW","offered","","CWE-310"
+"cipherlist_STRONG_NOFS","testssl.sh/81.169.166.184","443","OK","offered","",""
+"cipherlist_STRONG_FS","testssl.sh/81.169.166.184","443","OK","offered","",""
+"cipher_order-tls1","testssl.sh/81.169.166.184","443","OK","server","",""
+"cipher-tls1_xc014","testssl.sh/81.169.166.184","443","LOW","TLSv1 xc014 ECDHE-RSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_xc013","testssl.sh/81.169.166.184","443","LOW","TLSv1 xc013 ECDHE-RSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_x88","testssl.sh/81.169.166.184","443","LOW","TLSv1 x88 DHE-RSA-CAMELLIA256-SHA DH 2048 Camellia 256 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","",""
+"cipher-tls1_x45","testssl.sh/81.169.166.184","443","LOW","TLSv1 x45 DHE-RSA-CAMELLIA128-SHA DH 2048 Camellia 128 TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","",""
+"cipher-tls1_x39","testssl.sh/81.169.166.184","443","LOW","TLSv1 x39 DHE-RSA-AES256-SHA DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_x33","testssl.sh/81.169.166.184","443","LOW","TLSv1 x33 DHE-RSA-AES128-SHA DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_x35","testssl.sh/81.169.166.184","443","LOW","TLSv1 x35 AES256-SHA RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA","",""
+"cipherorder_TLSv1","testssl.sh/81.169.166.184","443","INFO","ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA DHE-RSA-CAMELLIA256-SHA DHE-RSA-CAMELLIA128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA AES256-SHA","",""
+"cipher_order-tls1_1","testssl.sh/81.169.166.184","443","OK","server","",""
+"cipher-tls1_1_xc014","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 xc014 ECDHE-RSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_1_xc013","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 xc013 ECDHE-RSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_1_x88","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 x88 DHE-RSA-CAMELLIA256-SHA DH 2048 Camellia 256 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","",""
+"cipher-tls1_1_x45","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 x45 DHE-RSA-CAMELLIA128-SHA DH 2048 Camellia 128 TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","",""
+"cipher-tls1_1_x39","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 x39 DHE-RSA-AES256-SHA DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_1_x33","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 x33 DHE-RSA-AES128-SHA DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_1_x35","testssl.sh/81.169.166.184","443","LOW","TLSv1.1 x35 AES256-SHA RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA","",""
+"cipherorder_TLSv1_1","testssl.sh/81.169.166.184","443","INFO","ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA DHE-RSA-CAMELLIA256-SHA DHE-RSA-CAMELLIA128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA AES256-SHA","",""
+"cipher_order-tls1_2","testssl.sh/81.169.166.184","443","OK","server","",""
+"cipher-tls1_2_xc030","testssl.sh/81.169.166.184","443","OK","TLSv1.2 xc030 ECDHE-RSA-AES256-GCM-SHA384 ECDH 256 AESGCM 256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","",""
+"cipher-tls1_2_xc02f","testssl.sh/81.169.166.184","443","OK","TLSv1.2 xc02f ECDHE-RSA-AES128-GCM-SHA256 ECDH 256 AESGCM 128 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","",""
+"cipher-tls1_2_x9f","testssl.sh/81.169.166.184","443","OK","TLSv1.2 x9f DHE-RSA-AES256-GCM-SHA384 DH 2048 AESGCM 256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","",""
+"cipher-tls1_2_x9e","testssl.sh/81.169.166.184","443","OK","TLSv1.2 x9e DHE-RSA-AES128-GCM-SHA256 DH 2048 AESGCM 128 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","",""
+"cipher-tls1_2_xc028","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 xc028 ECDHE-RSA-AES256-SHA384 ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","",""
+"cipher-tls1_2_xc014","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 xc014 ECDHE-RSA-AES256-SHA ECDH 256 AES 256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_2_xc013","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 xc013 ECDHE-RSA-AES128-SHA ECDH 256 AES 128 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_2_x88","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x88 DHE-RSA-CAMELLIA256-SHA DH 2048 Camellia 256 TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","",""
+"cipher-tls1_2_x45","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x45 DHE-RSA-CAMELLIA128-SHA DH 2048 Camellia 128 TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","",""
+"cipher-tls1_2_x6b","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x6b DHE-RSA-AES256-SHA256 DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","",""
+"cipher-tls1_2_x39","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x39 DHE-RSA-AES256-SHA DH 2048 AES 256 TLS_DHE_RSA_WITH_AES_256_CBC_SHA","",""
+"cipher-tls1_2_x67","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x67 DHE-RSA-AES128-SHA256 DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","",""
+"cipher-tls1_2_x33","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x33 DHE-RSA-AES128-SHA DH 2048 AES 128 TLS_DHE_RSA_WITH_AES_128_CBC_SHA","",""
+"cipher-tls1_2_x9d","testssl.sh/81.169.166.184","443","OK","TLSv1.2 x9d AES256-GCM-SHA384 RSA AESGCM 256 TLS_RSA_WITH_AES_256_GCM_SHA384","",""
+"cipher-tls1_2_x9c","testssl.sh/81.169.166.184","443","OK","TLSv1.2 x9c AES128-GCM-SHA256 RSA AESGCM 128 TLS_RSA_WITH_AES_128_GCM_SHA256","",""
+"cipher-tls1_2_x3d","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x3d AES256-SHA256 RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA256","",""
+"cipher-tls1_2_x35","testssl.sh/81.169.166.184","443","LOW","TLSv1.2 x35 AES256-SHA RSA AES 256 TLS_RSA_WITH_AES_256_CBC_SHA","",""
+"cipherorder_TLSv1_2","testssl.sh/81.169.166.184","443","INFO","ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA DHE-RSA-CAMELLIA256-SHA DHE-RSA-CAMELLIA128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA AES256-GCM-SHA384 AES128-GCM-SHA256 AES256-SHA256 AES256-SHA","",""
+"cipher_order-tls1_3","testssl.sh/81.169.166.184","443","OK","server","",""
+"cipher-tls1_3_x1302","testssl.sh/81.169.166.184","443","OK","TLSv1.3 x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384","",""
+"cipher-tls1_3_x1303","testssl.sh/81.169.166.184","443","OK","TLSv1.3 x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256","",""
+"cipher-tls1_3_x1301","testssl.sh/81.169.166.184","443","OK","TLSv1.3 x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256","",""
+"cipherorder_TLSv1_3","testssl.sh/81.169.166.184","443","INFO","TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256","",""
+"prioritize_chacha_TLSv1_3","testssl.sh/81.169.166.184","443","INFO","false","",""
+"cipher_order","testssl.sh/81.169.166.184","443","OK","server","",""
+"FS","testssl.sh/81.169.166.184","443","OK","offered","",""
+"FS_ciphers","testssl.sh/81.169.166.184","443","INFO","TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA DHE-RSA-CAMELLIA256-SHA TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-SHA DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-CAMELLIA128-SHA","",""
+"FS_ECDHE_curves","testssl.sh/81.169.166.184","443","OK","prime256v1 secp384r1 secp521r1 X25519 X448","",""
+"DH_groups","testssl.sh/81.169.166.184","443","OK","Unknown DH group (2048 bits)","",""
+"FS_TLS12_sig_algs","testssl.sh/81.169.166.184","443","INFO","RSA-PSS-RSAE+SHA256 RSA-PSS-RSAE+SHA384 RSA-PSS-RSAE+SHA512 RSA+SHA256 RSA+SHA384 RSA+SHA512 RSA+SHA224","",""
+"FS_TLS13_sig_algs","testssl.sh/81.169.166.184","443","INFO","RSA-PSS-RSAE+SHA256 RSA-PSS-RSAE+SHA384 RSA-PSS-RSAE+SHA512","",""
+"HTTP_status_code","testssl.sh/81.169.166.184","443","INFO","200 OK ('/')","",""
+"HTTP_clock_skew","testssl.sh/81.169.166.184","443","INFO","0 seconds from localtime","",""
+"HTTP_headerTime","testssl.sh/81.169.166.184","443","INFO","1654006271","",""
+"HSTS_time","testssl.sh/81.169.166.184","443","OK","362 days (=31337000 seconds) > 15552000 seconds","",""
+"HSTS_subdomains","testssl.sh/81.169.166.184","443","INFO","only for this domain","",""
+"HSTS_preload","testssl.sh/81.169.166.184","443","INFO","domain is NOT marked for preloading","",""
+"HPKP","testssl.sh/81.169.166.184","443","INFO","No support for HTTP Public Key Pinning","",""
+"banner_server","testssl.sh/81.169.166.184","443","INFO","Never trust a banner","",""
+"banner_application","testssl.sh/81.169.166.184","443","INFO","X-Powered-By: A portion of humor","",""
+"cookie_count","testssl.sh/81.169.166.184","443","INFO","0 at '/'","",""
+"X-Frame-Options","testssl.sh/81.169.166.184","443","OK","DENY","",""
+"X-Content-Type-Options","testssl.sh/81.169.166.184","443","OK","nosniff","",""
+"Content-Security-Policy","testssl.sh/81.169.166.184","443","OK","script-src 'unsafe-inline'; style-src 'unsafe-inline' 'self'; object-src 'self'; base-uri 'none'; form-action 'none'; img-src 'self' ; default-src 'self'; frame-ancestors 'self'; upgrade-insecure-requests;","",""
+"banner_reverseproxy","testssl.sh/81.169.166.184","443","INFO","--","","CWE-200"
+"heartbleed","testssl.sh/81.169.166.184","443","OK","not vulnerable, no heartbeat extension","CVE-2014-0160","CWE-119"
+"CCS","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2014-0224","CWE-310"
+"ticketbleed","testssl.sh/81.169.166.184","443","OK","no session ticket extension","CVE-2016-9244","CWE-200"
+"ROBOT","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2017-17382 CVE-2017-17427 CVE-2017-17428 CVE-2017-13098 CVE-2017-1000385 CVE-2017-13099 CVE-2016-6883 CVE-2012-5081 CVE-2017-6168","CWE-203"
+"secure_renego","testssl.sh/81.169.166.184","443","OK","supported","","CWE-310"
+"secure_client_renego","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2011-1473","CWE-310"
+"CRIME_TLS","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2012-4929","CWE-310"
+"BREACH","testssl.sh/81.169.166.184","443","OK","not vulnerable, no gzip/deflate/compress/br HTTP compression - only supplied '/' tested","CVE-2013-3587","CWE-310"
+"POODLE_SSL","testssl.sh/81.169.166.184","443","OK","not vulnerable, no SSLv3","CVE-2014-3566","CWE-310"
+"fallback_SCSV","testssl.sh/81.169.166.184","443","OK","supported","",""
+"SWEET32","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2016-2183 CVE-2016-6329","CWE-327"
+"FREAK","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2015-0204","CWE-310"
+"DROWN","testssl.sh/81.169.166.184","443","OK","not vulnerable on this host and port","CVE-2016-0800 CVE-2016-0703","CWE-310"
+"DROWN_hint","testssl.sh/81.169.166.184","443","INFO","Make sure you don't use this certificate elsewhere with SSLv2 enabled services, see https://search.censys.io/search?resource=hosts&virtual_hosts=INCLUDE&q=31B44391529821C6A77F3C78B02D716A07F99B8FDB342BF5A78F263C25375968","CVE-2016-0800 CVE-2016-0703","CWE-310"
+"LOGJAM","testssl.sh/81.169.166.184","443","OK","not vulnerable, no DH EXPORT ciphers,","CVE-2015-4000","CWE-310"
+"LOGJAM-common_primes","testssl.sh/81.169.166.184","443","OK","--","CVE-2015-4000","CWE-310"
+"BEAST_CBC_TLS1","testssl.sh/81.169.166.184","443","MEDIUM","ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA DHE-RSA-CAMELLIA256-SHA DHE-RSA-CAMELLIA128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA AES256-SHA","CVE-2011-3389","CWE-20"
+"BEAST","testssl.sh/81.169.166.184","443","LOW","VULNERABLE -- but also supports higher protocols TLSv1.1 TLSv1.2 (likely mitigated)","CVE-2011-3389","CWE-20"
+"LUCKY13","testssl.sh/81.169.166.184","443","LOW","potentially vulnerable, uses TLS CBC ciphers","CVE-2013-0169","CWE-310"
+"winshock","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2014-6321","CWE-94"
+"RC4","testssl.sh/81.169.166.184","443","OK","not vulnerable","CVE-2013-2566 CVE-2015-2808","CWE-310"
+"clientsimulation-android_60","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256","",""
+"clientsimulation-android_70","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-android_81","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-android_90","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-android_X","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-android_11","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-android_12","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-chrome_79_win10","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-chrome_101_win10","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-firefox_66_win81","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-firefox_100_win10","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-ie_6_xp","testssl.sh/81.169.166.184","443","INFO","No connection","",""
+"clientsimulation-ie_8_win7","testssl.sh/81.169.166.184","443","INFO","TLSv1.0 ECDHE-RSA-AES256-SHA","",""
+"clientsimulation-ie_8_xp","testssl.sh/81.169.166.184","443","INFO","No connection","",""
+"clientsimulation-ie_11_win7","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 DHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-ie_11_win81","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 DHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-ie_11_winphone81","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-SHA","",""
+"clientsimulation-ie_11_win10","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-edge_15_win10","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-edge_101_win10_21h2","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-safari_121_ios_122","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-safari_130_osx_10146","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-safari_154_osx_1231","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-java_7u25","testssl.sh/81.169.166.184","443","INFO","TLSv1.0 ECDHE-RSA-AES128-SHA","",""
+"clientsimulation-java_8u161","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-java1102","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-java1703","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-go_1178","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-libressl_283","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-openssl_102e","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-openssl_110l","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-openssl_111d","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-openssl_303","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""
+"clientsimulation-apple_mail_16_0","testssl.sh/81.169.166.184","443","INFO","TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384","",""
+"clientsimulation-thunderbird_91_9","testssl.sh/81.169.166.184","443","INFO","TLSv1.3 TLS_AES_256_GCM_SHA384","",""