Merge branch '2.9dev' into openssl_location

This commit is contained in:
David Cooper 2017-03-31 09:36:26 -04:00
commit e03d89107b
4 changed files with 285 additions and 224 deletions

View File

@ -16,6 +16,7 @@ my (
pass("Running testssl.sh against badssl.com to create a baseline (may take 2~3 minutes)"); $tests++; pass("Running testssl.sh against badssl.com to create a baseline (may take 2~3 minutes)"); $tests++;
my $okout = `./testssl.sh -S -e -U --jsonfile tmp.json --color 0 badssl.com`; my $okout = `./testssl.sh -S -e -U --jsonfile tmp.json --color 0 badssl.com`;
my $okjson = json('tmp.json'); my $okjson = json('tmp.json');
unlink 'tmp.json';
cmp_ok(@$okjson,'>',10,"We have more then 10 findings"); $tests++; cmp_ok(@$okjson,'>',10,"We have more then 10 findings"); $tests++;
# Expiration # Expiration
@ -23,6 +24,7 @@ pass("Running testssl against expired.badssl.com"); $tests++;
$out = `./testssl.sh -S --jsonfile tmp.json --color 0 expired.badssl.com`; $out = `./testssl.sh -S --jsonfile tmp.json --color 0 expired.badssl.com`;
like($out, qr/Certificate Expiration\s+expired\!/,"The certificate should be expired"); $tests++; like($out, qr/Certificate Expiration\s+expired\!/,"The certificate should be expired"); $tests++;
$json = json('tmp.json'); $json = json('tmp.json');
unlink 'tmp.json';
$found = 0; $found = 0;
foreach my $f ( @$json ) { foreach my $f ( @$json ) {
if ( $f->{id} eq "expiration" ) { if ( $f->{id} eq "expiration" ) {
@ -39,6 +41,7 @@ pass("Running testssl against self-signed.badssl.com"); $tests++;
$out = `./testssl.sh -S --jsonfile tmp.json --color 0 self-signed.badssl.com`; $out = `./testssl.sh -S --jsonfile tmp.json --color 0 self-signed.badssl.com`;
like($out, qr/Certificate Expiration\s+\d+/,"The certificate should not be expired"); $tests++; like($out, qr/Certificate Expiration\s+\d+/,"The certificate should not be expired"); $tests++;
$json = json('tmp.json'); $json = json('tmp.json');
unlink 'tmp.json';
$found = 0; $found = 0;
foreach my $f ( @$json ) { foreach my $f ( @$json ) {
if ( $f->{id} eq "expiration" ) { if ( $f->{id} eq "expiration" ) {
@ -79,6 +82,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++;
#$out = `./testssl.sh -S --jsonfile tmp.json --color 0 wrong.host.badssl.com`; #$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++; #unlike($out, qr/Certificate Expiration\s+expired\!/,"The certificate should not be expired"); $tests++;
#$json = json('tmp.json'); #$json = json('tmp.json');
#unlink 'tmp.json';
#$found = 0; #$found = 0;
#foreach my $f ( @$json ) { #foreach my $f ( @$json ) {
# if ( $f->{id} eq "expiration" ) { # if ( $f->{id} eq "expiration" ) {
@ -95,6 +99,7 @@ pass("Running testssl against incomplete-chain.badssl.com"); $tests++;
$out = `./testssl.sh -S --jsonfile tmp.json --color 0 incomplete-chain.badssl.com`; $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++; like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
$json = json('tmp.json'); $json = json('tmp.json');
unlink 'tmp.json';
$found = 0; $found = 0;
foreach my $f ( @$json ) { foreach my $f ( @$json ) {
if ( $f->{id} eq "chain_of_trust" ) { if ( $f->{id} eq "chain_of_trust" ) {
@ -113,6 +118,7 @@ is($found,1,"We had a finding for this in the JSON output"); $tests++;
#$out = `./testssl.sh -e -U --jsonfile tmp.json --color 0 cbc.badssl.com`; #$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++; #like($out, qr/Chain of trust.*?NOT ok\s+\(chain incomplete\)/,"Chain of trust should fail because of incomplete"); $tests++;
#$json = json('tmp.json'); #$json = json('tmp.json');
#unlink 'tmp.json';
#$found = 0; #$found = 0;
#foreach my $f ( @$json ) { #foreach my $f ( @$json ) {
# if ( $f->{id} eq "chain_of_trust" ) { # if ( $f->{id} eq "chain_of_trust" ) {

View File

@ -19,6 +19,7 @@ $tests = 0;
pass("Running testssl.sh against badssl.com to create a JSON report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++; pass("Running testssl.sh against badssl.com to create a JSON report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++;
$out = `./testssl.sh -S -e -U --jsonfile tmp.json --severity LOW --color 0 badssl.com`; $out = `./testssl.sh -S -e -U --jsonfile tmp.json --severity LOW --color 0 badssl.com`;
$json = json('tmp.json'); $json = json('tmp.json');
unlink 'tmp.json';
$found = 0; $found = 0;
cmp_ok(@$json,'>',0,"At least 1 finding is expected"); $tests++; cmp_ok(@$json,'>',0,"At least 1 finding is expected"); $tests++;
foreach my $f ( @$json ) { foreach my $f ( @$json ) {
@ -33,6 +34,7 @@ is($found,0,"We should not have any finding with INFO level"); $tests++;
pass("Running testssl.sh against badssl.com to create a JSON-PRETTY report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++; pass("Running testssl.sh against badssl.com to create a JSON-PRETTY report with severity level equal greater than LOW (may take 2~3 minutes)"); $tests++;
$out = `./testssl.sh -S -e -U --jsonfile-pretty tmp.json --severity LOW --color 0 badssl.com`; $out = `./testssl.sh -S -e -U --jsonfile-pretty tmp.json --severity LOW --color 0 badssl.com`;
$json_pretty = json('tmp.json'); $json_pretty = json('tmp.json');
unlink 'tmp.json';
$found = 0; $found = 0;
my $vulnerabilities = $json_pretty->{scanResult}->[0]->{vulnerabilities}; my $vulnerabilities = $json_pretty->{scanResult}->[0]->{vulnerabilities};
foreach my $f ( @$vulnerabilities ) { foreach my $f ( @$vulnerabilities ) {

View File

@ -16,6 +16,7 @@ my (
pass("Running testssl.sh against ssl.sectionzero.org"); $tests++; pass("Running testssl.sh against ssl.sectionzero.org"); $tests++;
$out = `./testssl.sh -H --jsonfile tmp.json --color 0 ssl.sectionzero.org`; $out = `./testssl.sh -H --jsonfile tmp.json --color 0 ssl.sectionzero.org`;
$json = json('tmp.json'); $json = json('tmp.json');
unlink 'tmp.json';
# It is better to have findings in a hash # It is better to have findings in a hash
# Look for a host cert match in the process. # Look for a host cert match in the process.

View File

@ -186,6 +186,7 @@ GIVE_HINTS=false # give an addtional info to findings
HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes HAS_IPv6=${HAS_IPv6:-false} # if you have OpenSSL with IPv6 support AND IPv6 networking set it to yes
UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses UNBRACKTD_IPV6=${UNBRACKTD_IPV6:-false} # some versions of OpenSSL (like Gentoo) don't support [bracketed] IPv6 addresses
SERVER_SIZE_LIMIT_BUG=false # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs) SERVER_SIZE_LIMIT_BUG=false # Some servers have either a ClientHello total size limit or a 128 cipher limit (e.g. old ASAs)
CHILD_MASS_TESTING=${CHILD_MASS_TESTING:-false}
# tuning vars, can not be set by a cmd line switch # tuning vars, can not be set by a cmd line switch
EXPERIMENTAL=${EXPERIMENTAL:-false} EXPERIMENTAL=${EXPERIMENTAL:-false}
@ -866,6 +867,8 @@ fileout_json_print_parameter() {
} }
fileout_json_finding() { fileout_json_finding() {
local target
if "$do_json"; then if "$do_json"; then
"$FIRST_FINDING" || echo -n "," >> "$JSONFILE" "$FIRST_FINDING" || echo -n "," >> "$JSONFILE"
echo -e " {" >> "$JSONFILE" echo -e " {" >> "$JSONFILE"
@ -884,9 +887,19 @@ fileout_json_finding() {
if [[ $SERVER_COUNTER -gt 1 ]]; then if [[ $SERVER_COUNTER -gt 1 ]]; then
echo " ," >> "$JSONFILE" echo " ," >> "$JSONFILE"
fi fi
if "$CHILD_MASS_TESTING" && ! "$JSONHEADER"; then
target="$NODE"
$do_mx_all_ips && target="$URI"
echo -e " {
\"target host\" : \"$target\",
\"port\" : \"$PORT\",
\"service\" : \"$finding\",
\"ip\" : \"$NODEIP\"," >> "$JSONFILE"
else
echo -e " { echo -e " {
\"service\" : \"$finding\", \"service\" : \"$finding\",
\"ip\" : \"$NODEIP\"," >> "$JSONFILE" \"ip\" : \"$NODEIP\"," >> "$JSONFILE"
fi
$do_mx_all_ips && echo -e " \"hostname\" : \"$NODE\"," >> "$JSONFILE" $do_mx_all_ips && echo -e " \"hostname\" : \"$NODE\"," >> "$JSONFILE"
else else
("$FIRST_FINDING" && echo -n " {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE" ("$FIRST_FINDING" && echo -n " {" >> "$JSONFILE") || echo -n ",{" >> "$JSONFILE"
@ -905,6 +918,21 @@ fileout_json_finding() {
##################### FILE FORMATING ######################### ##################### FILE FORMATING #########################
fileout_pretty_json_banner() { fileout_pretty_json_banner() {
local target
if "$do_mass_testing"; then
echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\",
\"at\" : \"$HNAME:$OPENSSL_LOCATION\",
\"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
\"openssl\" : \"$OSSL_VER from $OSSL_BUILD_DATE\",
\"startTime\" : \"$START_TIME\",
\"scanResult\" : ["
else
[[ -z "$NODE" ]] && parse_hn_port "${URI}"
# NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place
target="$NODE"
$do_mx_all_ips && target="$URI"
echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\", echo -e " \"Invocation\" : \"$PROG_NAME $CMDLINE\",
\"at\" : \"$HNAME:$OPENSSL_LOCATION\", \"at\" : \"$HNAME:$OPENSSL_LOCATION\",
\"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\", \"version\" : \"$VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\",
@ -913,12 +941,10 @@ fileout_pretty_json_banner() {
\"port\" : \"$PORT\", \"port\" : \"$PORT\",
\"startTime\" : \"$START_TIME\", \"startTime\" : \"$START_TIME\",
\"scanResult\" : [" \"scanResult\" : ["
fi
} }
fileout_banner() { fileout_banner() {
local target="$NODE"
$do_mx_all_ips && target="$URI"
#if ! "$APPEND"; then #if ! "$APPEND"; then
# if "$CSVHEADER"; then # if "$CSVHEADER"; then
# : # :
@ -930,6 +956,13 @@ fileout_banner() {
#fi #fi
} }
fileout_separator() {
if "$JSONHEADER"; then
"$do_pretty_json" && echo " ," >> "$JSONFILE"
"$do_json" && echo -n "," >> "$JSONFILE"
fi
}
fileout_footer() { fileout_footer() {
if "$JSONHEADER"; then if "$JSONHEADER"; then
fileout_json_footer fileout_json_footer
@ -956,18 +989,18 @@ fileout() {
json_header() { json_header() {
local fname_prefix local fname_prefix
local filename_provided=false
[[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] && filename_provided=true
# Similar to HTML: Don't create headers and footers in the following scenarios: # Similar to HTML: Don't create headers and footers in the following scenarios:
# * no JSON/CSV output is being created. # * no JSON/CSV output is being created.
# * mass testing is being performed and each test will have its own file. # * mass testing is being performed and each test will have its own file.
# * this is an individual test within a mass test and all output is being placed in a single file. # * this is an individual test within a mass test and all output is being placed in a single file.
! "$do_json" && ! "$do_pretty_json" && JSONHEADER=false && return 0
"$do_mass_testing" && ! "$filename_provided" && JSONHEADER=false && return 0
"$CHILD_MASS_TESTING" && "$filename_provided" && JSONHEADER=false && return 0
if ( ! "$do_json" && ! "$do_pretty_json" ) || \
( "$do_mass_testing" && ( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) ) || \
( "$APPEND" && [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]] ); then
JSONHEADER=false
return 0
fi
if "$do_display_only"; then if "$do_display_only"; then
fname_prefix="local-ciphers" fname_prefix="local-ciphers"
elif "$do_mass_testing"; then elif "$do_mass_testing"; then
@ -975,19 +1008,22 @@ json_header() {
elif "$do_mx_all_ips"; then elif "$do_mx_all_ips"; then
fname_prefix="mx-$URI" fname_prefix="mx-$URI"
else else
( [[ -z "$JSONFILE" ]] || [[ -d "$JSONFILE" ]] ) && parse_hn_port "${URI}" ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
# NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place
fname_prefix="${NODE}"_p"${PORT}" fname_prefix="${NODE}"_p"${PORT}"
fi fi
if [[ -n "$JSONFILE" ]] && [[ ! -d "$JSONFILE" ]]; then if [[ -z "$JSONFILE" ]]; then
rm -f "$JSONFILE"
elif [[ -z "$JSONFILE" ]]; then
JSONFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".json) JSONFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".json)
else elif [[ -d "$JSONFILE" ]]; then
JSONFILE=$JSONFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".json) JSONFILE=$JSONFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".json)
fi fi
if "$APPEND"; then
JSONHEADER=false
else
[[ -e "$JSONFILE" ]] && fatal "\"$JSONFILE\" exists. Either use \"--append\" or (re)move it" 1
"$do_json" && printf "[\n" > "$JSONFILE" "$do_json" && printf "[\n" > "$JSONFILE"
"$do_pretty_json" && printf "{\n" > "$JSONFILE" "$do_pretty_json" && printf "{\n" > "$JSONFILE"
fi
#FIRST_FINDING=false #FIRST_FINDING=false
return 0 return 0
} }
@ -995,14 +1031,15 @@ json_header() {
csv_header() { csv_header() {
local fname_prefix local fname_prefix
local filename_provided=false
[[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] && filename_provided=true
# CSV similar: # CSV similar:
if ! "$do_csv" || \ ! "$do_csv" && CSVHEADER=false && return 0
( "$do_mass_testing" && ( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) ) || \ "$do_mass_testing" && ! "$filename_provided" && CSVHEADER=false && return 0
( "$APPEND" && [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]] ); then "$CHILD_MASS_TESTING" && "$filename_provided" && CSVHEADER=false && return 0
CSVHEADER=false
return 0
fi
if "$do_display_only"; then if "$do_display_only"; then
fname_prefix="local-ciphers" fname_prefix="local-ciphers"
elif "$do_mass_testing"; then elif "$do_mass_testing"; then
@ -1010,18 +1047,22 @@ csv_header() {
elif "$do_mx_all_ips"; then elif "$do_mx_all_ips"; then
fname_prefix="mx-$URI" fname_prefix="mx-$URI"
else else
( [[ -z "$CSVFILE" ]] || [[ -d "$CSVFILE" ]] ) && parse_hn_port "${URI}" ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
# NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place
fname_prefix="${NODE}"_p"${PORT}" fname_prefix="${NODE}"_p"${PORT}"
fi fi
if [[ -n "$CSVFILE" ]] && [[ ! -d "$CSVFILE" ]]; then
rm -f "$CSVFILE" if [[ -z "$CSVFILE" ]]; then
elif [[ -z "$CSVFILE" ]]; then
CSVFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".csv) CSVFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".csv)
else elif [[ -d "$CSVFILE" ]]; then
CSVFILE=$CSVFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".csv) CSVFILE=$CSVFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".csv)
fi fi
"$do_csv" && echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE" if "$APPEND"; then
CSVHEADER=false
else
[[ -e "$CSVFILE" ]] && fatal "\"$CSVFILE\" exists. Either use \"--append\" or (re)move it" 1
echo "\"id\",\"fqdn/ip\",\"port\",\"severity\",\"finding\",\"cve\",\"cwe\",\"hint\"" > "$CSVFILE"
fi
return 0 return 0
} }
@ -1030,17 +1071,17 @@ csv_header() {
html_header() { html_header() {
local fname_prefix local fname_prefix
local filename_provided=false
[[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] && filename_provided=true
# Don't create HTML headers and footers in the following scenarios: # Don't create HTML headers and footers in the following scenarios:
# * HTML output is not being created. # * HTML output is not being created.
# * mass testing is being performed and each test will have its own HTML file. # * mass testing is being performed and each test will have its own HTML file.
# * this is an individual test within a mass test and all HTML output is being placed in a single file. # * this is an individual test within a mass test and all HTML output is being placed in a single file.
if ! "$do_html" || \ ! "$do_html" && HTMLHEADER=false && return 0
( "$do_mass_testing" && ( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) ) || \ "$do_mass_testing" && ! "$filename_provided" && HTMLHEADER=false && return 0
( "$APPEND" && [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]] ); then "$CHILD_MASS_TESTING" && "$filename_provided" && HTMLHEADER=false && return 0
HTMLHEADER=false
return 0
fi
if "$do_display_only"; then if "$do_display_only"; then
fname_prefix="local-ciphers" fname_prefix="local-ciphers"
@ -1049,18 +1090,20 @@ html_header() {
elif "$do_mx_all_ips"; then elif "$do_mx_all_ips"; then
fname_prefix="mx-$URI" fname_prefix="mx-$URI"
else else
( [[ -z "$HTMLFILE" ]] || [[ -d "$HTMLFILE" ]] ) && parse_hn_port "${URI}" ! "$filename_provided" && [[ -z "$NODE" ]] && parse_hn_port "${URI}"
# NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place # NODE, URL_PATH, PORT, IPADDR and IP46ADDR is set now --> wrong place
fname_prefix="${NODE}"_p"${PORT}" fname_prefix="${NODE}"_p"${PORT}"
fi fi
if [[ -n "$HTMLFILE" ]] && [[ ! -d "$HTMLFILE" ]]; then if [[ -z "$HTMLFILE" ]]; then
rm -f "$HTMLFILE"
elif [[ -z "$HTMLFILE" ]]; then
HTMLFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".html) HTMLFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".html)
else elif [[ -d "$HTMLFILE" ]]; then
HTMLFILE=$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html) HTMLFILE=$HTMLFILE/$fname_prefix-$(date +"%Y%m%d-%H%M".html)
fi fi
if "$APPEND"; then
HTMLHEADER=false
else
[[ -e "$HTMLFILE" ]] && fatal "\"$HTMLFILE\" exists. Either use \"--append\" or (re)move it" 1
html_out "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" html_out "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
html_out "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" html_out "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
html_out "<!-- This file was created with testssl.sh. https://testssl.sh -->\n" html_out "<!-- This file was created with testssl.sh. https://testssl.sh -->\n"
@ -1071,11 +1114,12 @@ html_header() {
html_out "</head>\n" html_out "</head>\n"
html_out "<body>\n" html_out "<body>\n"
html_out "<pre>\n" html_out "<pre>\n"
fi
return 0 return 0
} }
html_banner() { html_banner() {
if "$APPEND" && "$HTMLHEADER"; then if "$CHILD_MASS_TESTING" && "$HTMLHEADER"; then
html_out "## Scan started as: \"$PROG_NAME $CMDLINE\"\n" html_out "## Scan started as: \"$PROG_NAME $CMDLINE\"\n"
html_out "## at $HNAME:$OPENSSL_LOCATION\n" html_out "## at $HNAME:$OPENSSL_LOCATION\n"
html_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\n" html_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE\n"
@ -1102,8 +1146,8 @@ if [[ $(uname) == "Linux" ]] ; then
toupper() { echo -n "${1^^}" ; } toupper() { echo -n "${1^^}" ; }
tolower() { echo -n "${1,,}" ; } tolower() { echo -n "${1,,}" ; }
else else
toupper() { echo -n "$1" | tr 'a-z' 'A-Z'; } toupper() { tr 'a-z' 'A-Z' <<< "$1"; }
tolower() { echo -n "$1" | tr 'A-Z' 'a-z' ; } tolower() { tr 'A-Z' 'a-z' <<< "$1"; }
fi fi
debugme() { debugme() {
@ -1137,7 +1181,7 @@ count_words() {
} }
count_ciphers() { count_ciphers() {
echo -n "$1" | sed 's/:/ /g' | wc -w | sed 's/ //g' echo $(wc -w <<< "${1//:/ }")
} }
actually_supported_ciphers() { actually_supported_ciphers() {
@ -1432,7 +1476,6 @@ service_detection() {
head $TMPFILE | egrep -aqw "Jive News|InterNetNews|NNRP|INN" && SERVICE=NNTP head $TMPFILE | egrep -aqw "Jive News|InterNetNews|NNRP|INN" && SERVICE=NNTP
debugme head -50 $TMPFILE debugme head -50 $TMPFILE
fi fi
# FIXME: we can guess ports by port number if not properly recognized (and label it as guessed)
out " Service detected: $CORRECT_SPACES" out " Service detected: $CORRECT_SPACES"
case $SERVICE in case $SERVICE in
@ -2450,7 +2493,7 @@ std_cipherlists() {
;; ;;
esac esac
tmpfile_handle $FUNCNAME.$debugname.txt tmpfile_handle $FUNCNAME.$debugname.txt
[[ $DEBUG -ge 1 ]] && outln " -- $1" || outln #FIXME: should be in standard output at some time [[ $DEBUG -ge 1 ]] && tmln_out " -- $1" || tmln_out
else else
singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g' <<< "$2") singlespaces=$(sed -e 's/ \+/ /g' -e 's/^ //' -e 's/ $//g' -e 's/ //g' <<< "$2")
if [[ "$OPTIMAL_PROTO" == "-ssl2" ]]; then if [[ "$OPTIMAL_PROTO" == "-ssl2" ]]; then
@ -6080,18 +6123,16 @@ certificate_info() {
run_server_defaults() { run_server_defaults() {
local ciph match_found newhostcert sni local ciph newhostcert sni
local sessticket_str="" local match_found
local lifetime unit local sessticket_str="" lifetime unit
local line
local -i i n local -i i n
local -i certs_found=0 local -i certs_found=0
local -a previous_hostcert previous_intermediates keysize cipher local -a previous_hostcert previous_intermediates keysize cipher
local -a ocsp_response ocsp_response_status sni_used local -a ocsp_response ocsp_response_status sni_used
local -a ciphers_to_test success local -a ciphers_to_test
local -a -i success
local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions local cn_nosni cn_sni sans_nosni sans_sni san tls_extensions
local alpn_proto alpn="" alpn_list_len_hex alpn_extn_len_hex success
local -i alpn_list_len alpn_extn_len
# Try each public key type once: # Try each public key type once:
# ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys # ciphers_to_test[1]: cipher suites using certificates with RSA signature public keys
@ -6168,8 +6209,9 @@ run_server_defaults() {
if [[ ${success[n]} -ne 0 ]]; then if [[ ${success[n]} -ne 0 ]]; then
cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")" cn_nosni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
sans_nosni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ sans_nosni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
tr ',' '\n' | grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")" grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
echo "${previous_hostcert[1]}" > $HOSTCERT echo "${previous_hostcert[1]}" > $HOSTCERT
cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")" cn_sni="$(toupper "$(get_cn_from_cert $HOSTCERT)")"
@ -6179,8 +6221,9 @@ run_server_defaults() {
# match if the CNs are the same and the SANs (if # match if the CNs are the same and the SANs (if
# present) contain at least one DNS name in common. # present) contain at least one DNS name in common.
if [[ "$cn_nosni" == "$cn_sni" ]]; then if [[ "$cn_nosni" == "$cn_sni" ]]; then
sans_sni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | grep -A2 "Subject Alternative Name" | \ sans_sni="$(toupper "$($OPENSSL x509 -in $HOSTCERT -noout -text 2>>$ERRFILE | \
tr ',' '\n' | grep "DNS:" | sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")" grep -A2 "Subject Alternative Name" | tr ',' '\n' | grep "DNS:" | \
sed -e 's/DNS://g' -e 's/ //g' | tr '\n' ' ')")"
if [[ "$sans_nosni" == "$sans_sni" ]]; then if [[ "$sans_nosni" == "$sans_sni" ]]; then
success[n]=0 success[n]=0
else else
@ -6249,7 +6292,7 @@ run_server_defaults() {
unit=$(grep -a lifetime <<< "$sessticket_str" | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g') unit=$(grep -a lifetime <<< "$sessticket_str" | sed -e 's/^.*'"$lifetime"'//' -e 's/[ ()]//g')
out "$lifetime $unit " out "$lifetime $unit "
prln_svrty_low "(PFS requires session ticket keys to be rotated <= daily)" prln_svrty_low "(PFS requires session ticket keys to be rotated <= daily)"
fileout "session_ticket" "LOW" "TLS session tickes RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)" fileout "session_ticket" "LOW" "TLS session ticket RFC 5077 valid for $lifetime $unit (PFS requires session ticket keys to be rotated at least daily)"
fi fi
pr_bold " SSL Session ID support " pr_bold " SSL Session ID support "
@ -10111,7 +10154,7 @@ run_beast(){
fi fi
else else
if ! "$vuln_beast" ; then if ! "$vuln_beast" ; then
prln_done_good " no CBC ciphers for $(toupper $proto) (OK)" prln_done_good "no CBC ciphers for $(toupper $proto) (OK)"
fileout "cbc_$proto" "OK" "BEAST: No CBC ciphers for $(toupper $proto)" "$cve" "$cwe" fileout "cbc_$proto" "OK" "BEAST: No CBC ciphers for $(toupper $proto)" "$cve" "$cwe"
fi fi
fi fi
@ -10831,7 +10874,7 @@ EOF
} }
maketempf() { maketempf() {
TEMPDIR=$(mktemp -d /tmp/ssltester.XXXXXX) || exit -6 TEMPDIR=$(mktemp -d /tmp/testssl.XXXXXX) || exit -6
TMPFILE=$TEMPDIR/tempfile.txt || exit -6 TMPFILE=$TEMPDIR/tempfile.txt || exit -6
if [[ "$DEBUG" -eq 0 ]]; then if [[ "$DEBUG" -eq 0 ]]; then
ERRFILE="/dev/null" ERRFILE="/dev/null"
@ -10957,7 +11000,8 @@ mybanner() {
local idtag local idtag
local bb1 bb2 bb3 local bb1 bb2 bb3
$QUIET && return "$QUIET" && return
"$CHILD_MASS_TESTING" && return
OPENSSL_NR_CIPHERS=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)") OPENSSL_NR_CIPHERS=$(count_ciphers "$($OPENSSL ciphers 'ALL:COMPLEMENTOFALL:@STRENGTH' 2>/dev/null)")
[[ -z "$GIT_REL" ]] && \ [[ -z "$GIT_REL" ]] && \
idtag="$CVS_REL" || \ idtag="$CVS_REL" || \
@ -11133,10 +11177,17 @@ parse_hn_port() {
# arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names # arg1: for testing mx records name we put a name of logfile in here, otherwise we get strange file names
prepare_logging() { prepare_logging() {
local fname_prefix="$1" local fname_prefix="$1"
local filename_provided=false
[[ -n "$LOGFILE" ]] && [[ ! -d "$LOGFILE" ]] && filename_provided=true
# Similar to html_header():
! "$do_logging" && return 0
"$do_mass_testing" && ! "$filename_provided" && return 0
"$CHILD_MASS_TESTING" && "$filename_provided" && return 0
[[ -z "$fname_prefix" ]] && fname_prefix="${NODE}"_p"${PORT}" [[ -z "$fname_prefix" ]] && fname_prefix="${NODE}"_p"${PORT}"
if "$do_logging"; then
if [[ -z "$LOGFILE" ]]; then if [[ -z "$LOGFILE" ]]; then
LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log) LOGFILE=$fname_prefix-$(date +"%Y%m%d-%H%M".log)
elif [[ -d "$LOGFILE" ]]; then elif [[ -d "$LOGFILE" ]]; then
@ -11148,15 +11199,12 @@ prepare_logging() {
if ! "$APPEND"; then if ! "$APPEND"; then
[[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1 [[ -e $LOGFILE ]] && fatal "\"$LOGFILE\" exists. Either use \"--append\" or (re)move it" 1
else
>$LOGFILE
fi fi
tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE} tmln_out "## Scan started as: \"$PROG_NAME $CMDLINE\"" >>${LOGFILE}
tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE} tmln_out "## at $HNAME:$OPENSSL_LOCATION" >>${LOGFILE}
tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE} tmln_out "## version testssl: $VERSION ${GIT_REL_SHORT:-$CVS_REL_SHORT} from $REL_DATE" >>${LOGFILE}
tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE} tmln_out "## version openssl: \"$OSSL_VER\" from \"$OSSL_BUILD_DATE\")\n" >>${LOGFILE}
exec > >(tee -a ${LOGFILE}) exec > >(tee -a ${LOGFILE})
fi
} }
@ -11169,9 +11217,9 @@ filter_ip6_address() {
continue continue
fi fi
if "$HAS_SED_E"; then if "$HAS_SED_E"; then
echo "$a" | sed -E 's/^abcdeABCDEFf0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d' sed -E 's/^abcdeABCDEFf0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d'
else else
echo "$a" | sed -r 's/[^abcdefABCDEF0123456789:]//g' | sed -e '/^$/d' -e '/^;;/d' sed -r 's/[^abcdefABCDEF0123456789:]//g' <<< "$a" | sed -e '/^$/d' -e '/^;;/d'
fi fi
done done
} }
@ -11184,9 +11232,9 @@ filter_ip4_address() {
continue continue
fi fi
if "$HAS_SED_E"; then if "$HAS_SED_E"; then
echo "$a" | sed -E 's/[^[:digit:].]//g' | sed -e '/^$/d' sed -E 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d'
else else
echo "$a" | sed -r 's/[^[:digit:].]//g' | sed -e '/^$/d' sed -r 's/[^[:digit:].]//g' <<< "$a" | sed -e '/^$/d'
fi fi
done done
} }
@ -11529,7 +11577,7 @@ sclient_auth() {
# this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances # this function determines OPTIMAL_PROTO. It is a workaround function as under certain circumstances
# (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail! # (e.g. IIS6.0 and openssl 1.0.2 as opposed to 1.0.1) needs a protocol otherwise s_client -connect will fail!
# Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap # Circumstances observed so far: 1.) IIS 6 2.) starttls + dovecot imap
# The first try in the loop is empty as we prefer not to specify always a protocol if it works w/o. # The first try in the loop is empty as we prefer not to specify always a protocol if we can get along w/o it
# #
determine_optimal_proto() { determine_optimal_proto() {
local all_failed local all_failed
@ -11610,9 +11658,9 @@ determine_service() {
ua="$UA_SNEAKY" || \ ua="$UA_SNEAKY" || \
ua="$UA_STD" ua="$UA_STD"
GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n" GET_REQ11="GET $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n"
#HEAD_REQ11="HEAD $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n" # HEAD_REQ11="HEAD $URL_PATH HTTP/1.1\r\nHost: $NODE\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n"
#GET_REQ10="GET $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n" # GET_REQ10="GET $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nConnection: Close\r\nAccept: text/*\r\n\r\n"
#HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n" # HEAD_REQ10="HEAD $URL_PATH HTTP/1.0\r\nUser-Agent: $ua\r\nAccept: text/*\r\n\r\n"
service_detection $OPTIMAL_PROTO service_detection $OPTIMAL_PROTO
else else
# STARTTLS # STARTTLS
@ -11762,9 +11810,35 @@ run_mx_all_ips() {
return $ret return $ret
} }
run_mass_testing() {
local cmdline=""
local first=true
local global_cmdline=${CMDLINE%%--file*} # $global_cmdline may have arguments in addition to the one in the file
if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
fatal "Can't read file \"$FNAME\"" "2"
fi
pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
while read cmdline; do
cmdline=$(filter_input "$cmdline")
[[ -z "$cmdline" ]] && continue
[[ "$cmdline" == "EOF" ]] && break
cmdline="$0 $global_cmdline --warnings=batch $cmdline"
draw_line "=" $((TERM_WIDTH / 2)); outln;
outln "$cmdline"
"$first" || fileout_separator # this is needed for appended output, see #687
CHILD_MASS_TESTING=true $cmdline # we call ourselves here. $do_mass_testing is the parent, $CHILD_MASS_TESTING... you figured
first=false
done < "${FNAME}"
return $?
}
#FIXME: not called/tested yet
run_mass_testing_parallel() { run_mass_testing_parallel() {
local cmdline="" local cmdline=""
local first=true
local global_cmdline=${CMDLINE%%--file*} local global_cmdline=${CMDLINE%%--file*}
if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then if [[ ! -r "$FNAME" ]] && $IKNOW_FNAME; then
@ -11777,40 +11851,17 @@ run_mass_testing_parallel() {
cmdline=$(filter_input "$cmdline") cmdline=$(filter_input "$cmdline")
[[ -z "$cmdline" ]] && continue [[ -z "$cmdline" ]] && continue
[[ "$cmdline" == "EOF" ]] && break [[ "$cmdline" == "EOF" ]] && break
cmdline="$0 $global_cmdline --warnings=batch -q $cmdline" cmdline="$0 $global_cmdline --warnings=batch $cmdline"
draw_line "=" $((TERM_WIDTH / 2)); outln; draw_line "=" $((TERM_WIDTH / 2)); outln;
determine_logfile
outln "$cmdline" outln "$cmdline"
$cmdline >$LOGFILE & CHILD_MASS_TESTING=true $cmdline >$LOGFILE &
# first=false
sleep $PARALLEL_SLEEP sleep $PARALLEL_SLEEP
done < "$FNAME" done < "$FNAME"
return $? return $?
} }
run_mass_testing() {
local cmdline=""
local global_cmdline=${CMDLINE%%--file*}
if [[ ! -r "$FNAME" ]] && "$IKNOW_FNAME"; then
fatal "Can't read file \"$FNAME\"" "2"
fi
pr_reverse "====== Running in file batch mode with file=\"$FNAME\" ======"; outln "\n"
APPEND=false # Make sure we close out our files
while read cmdline; do
cmdline=$(filter_input "$cmdline")
[[ -z "$cmdline" ]] && continue
[[ "$cmdline" == "EOF" ]] && break
cmdline="$0 $global_cmdline --warnings=batch -q --append $cmdline"
draw_line "=" $((TERM_WIDTH / 2)); outln;
outln "$cmdline"
$cmdline
done < "${FNAME}"
return $?
}
# This initializes boolean global do_* variables. They keep track of what to do # This initializes boolean global do_* variables. They keep track of what to do
# -- as the name insinuates # -- as the name insinuates
@ -12439,9 +12490,6 @@ ip=""
lets_roll init lets_roll init
initialize_globals initialize_globals
parse_cmd_line "$@" parse_cmd_line "$@"
json_header
csv_header
html_header
get_install_dir get_install_dir
set_color_functions set_color_functions
maketempf maketempf
@ -12452,20 +12500,24 @@ mybanner
check_proxy check_proxy
check4openssl_oldfarts check4openssl_oldfarts
check_bsd_mount check_bsd_mount
json_header
csv_header
html_header
if $do_display_only; then if "$do_display_only"; then
prettyprint_local "$PATTERN2SHOW" prettyprint_local "$PATTERN2SHOW"
exit $? exit $?
fi fi
if $do_mass_testing; then fileout_banner
if "$do_mass_testing"; then
prepare_logging prepare_logging
run_mass_testing run_mass_testing
exit $? exit $?
fi fi
html_banner html_banner
fileout_banner
#TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser #TODO: there shouldn't be the need for a special case for --mx, only the ip adresses we would need upfront and the do-parser
if $do_mx_all_ips; then if $do_mx_all_ips; then