Skip to content

Commit a292921

Browse files
authored
Merge pull request #1697 from drwetter/no_starttls_detection2
Trying to address no STARTTLS offerings (2)
2 parents ee7a21e + 1915a7b commit a292921

2 files changed

Lines changed: 95 additions & 64 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* Security fix: DNS input
1616
* Don't use external pwd anymore
1717
* STARTTLS: XMPP server support
18+
* Code improvements to STARTTLS
19+
* Detect better when no STARTTLS is offered
1820
* Rating (SSL Labs, not complete)
1921
* Don't penalize missing trust in rating when CA not in Java store
2022
* Added support for certificates with EdDSA signatures and pubilc keys

testssl.sh

Lines changed: 93 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ IGN_OCSP_PROXY=${IGN_OCSP_PROXY:-false} # Also when --proxy is supplied it is ig
197197
HEADER_MAXSLEEP=${HEADER_MAXSLEEP:-5} # we wait this long before killing the process to retrieve a service banner / http header
198198
MAX_SOCKET_FAIL=${MAX_SOCKET_FAIL:-2} # If this many failures for TCP socket connects are reached we terminate
199199
MAX_OSSL_FAIL=${MAX_OSSL_FAIL:-2} # If this many failures for s_client connects are reached we terminate
200+
MAX_STARTTLS_FAIL=${MAX_STARTTLS_FAIL:-2} # max number of STARTTLS handshake failures in plaintext phase
200201
MAX_HEADER_FAIL=${MAX_HEADER_FAIL:-2} # If this many failures for HTTP GET are encountered we don't try again to get the header
201202
MAX_WAITSOCK=${MAX_WAITSOCK:-10} # waiting at max 10 seconds for socket reply. There shouldn't be any reason to change this.
202203
CCS_MAX_WAITSOCK=${CCS_MAX_WAITSOCK:-5} # for the two CCS payload (each). There shouldn't be any reason to change this.
@@ -252,6 +253,7 @@ TIMEOUT_CMD=""
252253
HAD_SLEPT=0
253254
NR_SOCKET_FAIL=0 # Counter for socket failures
254255
NR_OSSL_FAIL=0 # .. for OpenSSL connects
256+
NR_STARTTLS_FAIL=0 # .. for STARTTLS failures
255257
NR_HEADER_FAIL=0 # .. for HTTP_GET
256258
PROTOS_OFFERED="" # This keeps which protocol is being offered. See has_server_protocol().
257259
TLS12_CIPHER_OFFERED="" # This contains the hexcode of a cipher known to be supported by the server with TLS 1.2
@@ -10415,16 +10417,19 @@ starttls_io() {
1041510417

1041610418

1041710419
# Line-based send with newline characters appended (arg2 empty)
10418-
# Stream-based send (not in use currently): arg2: <any>.
10420+
# arg2: debug_string -- what we had in the caller previously
1041910421
starttls_just_send(){
10420-
if [[ -z "$2" ]] ; then
10421-
debugme echo "C: $1\r\n"
10422-
echo -ne "$1\r\n" >&5
10422+
local -i ret=0
10423+
10424+
debugme echo "C: $1\r\n"
10425+
echo -ne "$1\r\n" >&5
10426+
ret=$?
10427+
if [[ $ret -eq 0 ]]; then
10428+
debugme echo " > succeeded: $2"
1042310429
else
10424-
debugme echo -e "C: $1"
10425-
echo -ne "$1" >&5
10430+
debugme echo " > failed: $2 ($ret)"
1042610431
fi
10427-
return $?
10432+
return $ret
1042810433
}
1042910434

1043010435
# arg1: (optional): wait time
@@ -10444,66 +10449,74 @@ starttls_just_read(){
1044410449
starttls_full_read(){
1044510450
local cont_pattern="$1"
1044610451
local end_pattern="$2"
10447-
local regex="$3"
10452+
local starttls_regex="$3" # optional: pattern we search for in the server's response
10453+
local debug_str="$4" # optional
1044810454
local starttls_read_data=()
1044910455
local one_line=""
1045010456
local ret=0
1045110457
local ret_found=0
1045210458
local debugpad=" > found: "
10459+
local oldIFS="$IFS"
1045310460

1045410461
debugme echo "=== reading banner ... ==="
10455-
if [[ $# -ge 3 ]]; then
10456-
debugme echo "=== we'll have to search for \"$regex\" pattern ==="
10462+
if [[ -n "$starttls_regex" ]]; then
10463+
debugme echo "=== we'll have to search for \"$starttls_regex\" pattern ==="
10464+
# pre-set an error if we won't find the ~regex
1045710465
ret_found=3
1045810466
fi
1045910467

10460-
local oldIFS="$IFS"
1046110468
IFS=''
10469+
# Now read handshake line by line and act on the args supplied.
10470+
# Exit the subshell if timeout has been hit (-t $STARTTLS_SLEEP)
1046210471
while read -r -t $STARTTLS_SLEEP one_line; ret=$?; (exit $ret); do
1046310472
debugme tmln_out "S: ${one_line}"
1046410473
if [[ $DEBUG -ge 5 ]]; then
1046510474
echo "end_pattern/cont_pattern: ${end_pattern} / ${cont_pattern}"
1046610475
fi
10467-
if [[ $# -ge 3 ]]; then
10468-
if [[ ${one_line} =~ $regex ]]; then
10469-
ret_found=0
10476+
if [[ -n "$starttls_regex" ]]; then
10477+
if [[ ${one_line} =~ $starttls_regex ]]; then
1047010478
debugme tmln_out "${debugpad} ${one_line} "
10479+
# We don't exit here as the buffer is not empty. So we continue reading but save the status:
10480+
ret_found=0
1047110481
fi
1047210482
fi
1047310483
starttls_read_data+=("${one_line}")
1047410484
if [[ ${one_line} =~ ${end_pattern} ]]; then
1047510485
debugme tmln_out "${debugpad} ${one_line} "
1047610486
IFS="${oldIFS}"
10477-
return ${ret_found}
10487+
break
1047810488
fi
1047910489
if [[ ! ${one_line} =~ ${cont_pattern} ]]; then
1048010490
debugme echo "=== full read syntax error, expected regex pattern ${cont_pattern} (cont) or ${end_pattern} (end) ==="
1048110491
IFS="${oldIFS}"
10482-
return 2
10492+
ret_found=2
10493+
break
1048310494
fi
1048410495
done <&5
10485-
if [[ $DEBUG -ge 2 ]]; then
10496+
if [[ $ret_found -eq 0 ]]; then
10497+
# Print the debug statement we previously had in the caller function
10498+
[[ -n "$debug_str" ]] && debugme echo " >> $debug_str"
10499+
else
1048610500
if [[ $ret -ge 128 ]]; then
10487-
echo "=== timeout reading ==="
10488-
else
10489-
echo "=== full read error (no timeout) ==="
10501+
debugme echo "=== timeout reading ==="
10502+
$ret_found=$ret
1049010503
fi
1049110504
fi
1049210505
IFS="${oldIFS}"
10493-
return $ret
10506+
return $ret_found
1049410507
}
1049510508

1049610509
starttls_ftp_dialog() {
10497-
local debugpad=" > "
10498-
local reAUTHTLS='^ AUTH TLS'
10510+
local -i ret=0
10511+
local reSTARTTLS='^ AUTH TLS'
1049910512

1050010513
debugme echo "=== starting ftp STARTTLS dialog ==="
10501-
starttls_full_read '^220-' '^220 ' && debugme echo "${debugpad}received server greeting" &&
10502-
starttls_just_send 'FEAT' && debugme echo "${debugpad}sent FEAT" &&
10503-
starttls_full_read '^(211-| )' '^211 ' "${reAUTHTLS}" && debugme echo "${debugpad}received server features and checked STARTTLS availability" &&
10504-
starttls_just_send 'AUTH TLS' && debugme echo "${debugpad}initiated STARTTLS" &&
10505-
starttls_full_read '^234-' '^234 ' && debugme echo "${debugpad}received ack for STARTTLS"
10506-
local ret=$?
10514+
starttls_full_read '^220-' '^220 ' '' "received server greeting" &&
10515+
starttls_just_send 'FEAT' "sent FEAT" &&
10516+
starttls_full_read '^(211-| )' '^211 ' "${reSTARTTLS}" "received server features and checked STARTTLS availability" &&
10517+
starttls_just_send 'AUTH TLS' "initiated STARTTLS" &&
10518+
starttls_full_read '^234-' '^234 ' '' "received ack for STARTTLS"
10519+
ret=$?
1050710520
debugme echo "=== finished ftp STARTTLS dialog with ${ret} ==="
1050810521
return $ret
1050910522
}
@@ -10513,59 +10526,61 @@ starttls_ftp_dialog() {
1051310526
starttls_smtp_dialog() {
1051410527
local greet_str="EHLO testssl.sh"
1051510528
local proto="smtp"
10516-
local re250STARTTLS='^250[ -]STARTTLS'
10517-
local debugpad=" > "
10529+
local reSTARTTLS='^250[ -]STARTTLS'
10530+
local -i ret=0
1051810531

1051910532
if [[ "$1" == lmtp ]]; then
1052010533
proto="lmtp"
1052110534
greet_str="LHLO"
1052210535
fi
1052310536
if [[ -n "$2" ]]; then
10524-
# Here we can "add" commands in the future. Please note \r\n will automatically appended
10537+
# Here we can "add" commands in the future. Please note \r\n will automatically be appended
1052510538
greet_str="$2"
1052610539
elif "$SNEAKY"; then
1052710540
greet_str="EHLO google.com"
1052810541
fi
1052910542
debugme echo "=== starting $proto STARTTLS dialog ==="
1053010543

10531-
starttls_full_read '^220-' '^220 ' && debugme echo "${debugpad}received server greeting" &&
10532-
starttls_just_send "$greet_str" && debugme echo "${debugpad}sent $greet_str" &&
10533-
starttls_full_read '^250-' '^250 ' "${re250STARTTLS}" && debugme echo "${debugpad}received server capabilities and checked STARTTLS availability" &&
10534-
starttls_just_send 'STARTTLS' && debugme echo "${debugpad}initiated STARTTLS" &&
10535-
starttls_full_read '^220-' '^220 ' && debugme echo "${debugpad}received ack for STARTTLS"
10536-
local ret=$?
10544+
starttls_full_read '^220-' '^220 ' '' "received server greeting" &&
10545+
starttls_just_send "$greet_str" "sent $greet_str" &&
10546+
starttls_full_read '^250-' '^250 ' "${reSTARTTLS}" "received server capabilities and checked STARTTLS availability" &&
10547+
starttls_just_send 'STARTTLS' "initiated STARTTLS" &&
10548+
starttls_full_read '^220-' '^220 ' '' "received ack for STARTTLS"
10549+
ret=$?
1053710550
debugme echo "=== finished $proto STARTTLS dialog with ${ret} ==="
1053810551
return $ret
1053910552
}
1054010553

1054110554
starttls_pop3_dialog() {
10542-
local debugpad=" > "
10555+
local -i ret=0
1054310556

1054410557
debugme echo "=== starting pop3 STARTTLS dialog ==="
10545-
starttls_full_read '^\+OK' '^\+OK' && debugme echo "${debugpad}received server greeting" &&
10546-
starttls_just_send 'STLS' && debugme echo "${debugpad}initiated STARTTLS" &&
10547-
starttls_full_read '^\+OK' '^\+OK' && debugme echo "${debugpad}received ack for STARTTLS"
10548-
local ret=$?
10558+
starttls_full_read '^\+OK' '^\+OK' '' "received server greeting" &&
10559+
starttls_just_send 'STLS' "initiated STARTTLS" &&
10560+
starttls_full_read '^\+OK' '^\+OK' '' "received ack for STARTTLS"
10561+
ret=$?
1054910562
debugme echo "=== finished pop3 STARTTLS dialog with ${ret} ==="
1055010563
return $ret
1055110564
}
1055210565

1055310566
starttls_imap_dialog() {
10567+
local -i ret=0
1055410568
local reSTARTTLS='^\* CAPABILITY(( .*)? IMAP4rev1( .*)? STARTTLS(.*)?|( .*)? STARTTLS( .*)? IMAP4rev1(.*)?)$'
10555-
local debugpad=" > "
1055610569

1055710570
debugme echo "=== starting imap STARTTLS dialog ==="
10558-
starttls_full_read '^\* ' '^\* OK ' && debugme echo "${debugpad}received server greeting" &&
10559-
starttls_just_send 'a001 CAPABILITY' && debugme echo "${debugpad}sent CAPABILITY" &&
10560-
starttls_full_read '^\* ' '^a001 OK ' "${reSTARTTLS}" && debugme echo "${debugpad}received server capabilities and checked STARTTLS availability" &&
10561-
starttls_just_send 'a002 STARTTLS' && debugme echo "${debugpad}initiated STARTTLS" &&
10562-
starttls_full_read '^\* ' '^a002 OK ' && debugme echo "${debugpad}received ack for STARTTLS"
10563-
local ret=$?
10571+
starttls_full_read '^\* ' '^\* OK ' '' "received server greeting" &&
10572+
starttls_just_send 'a001 CAPABILITY' "sent CAPABILITY" &&
10573+
starttls_full_read '^\* ' '^a001 OK ' "${reSTARTTLS}" "received server capabilities and checked STARTTLS availability" &&
10574+
starttls_just_send 'a002 STARTTLS' "initiated STARTTLS" &&
10575+
starttls_full_read '^\* ' '^a002 OK ' '' "received ack for STARTTLS"
10576+
ret=$?
1056410577
debugme echo "=== finished imap STARTTLS dialog with ${ret} ==="
1056510578
return $ret
1056610579
}
1056710580

1056810581
starttls_xmpp_dialog() {
10582+
local -i ret=0
10583+
1056910584
debugme echo "=== starting xmpp STARTTLS dialog ==="
1057010585
[[ -z $XMPP_HOST ]] && XMPP_HOST="$NODE"
1057110586

@@ -10575,36 +10590,40 @@ starttls_xmpp_dialog() {
1057510590
starttls_io "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='"$namespace"' to='"$XMPP_HOST"' version='1.0'>" 'starttls(.*)features' 1 &&
1057610591
starttls_io "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>" '<proceed' 1
1057710592
# starttls_io "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='"$namespace"' to='"$XMPP_HOST"' version='1.0'>" 'JUSTSEND' 2
10578-
local ret=$?
10593+
ret=$?
1057910594
debugme echo "=== finished xmpp STARTTLS dialog with ${ret} ==="
1058010595
return $ret
1058110596
}
1058210597

1058310598
starttls_nntp_dialog() {
10584-
local debugpad=" > "
10599+
local -i ret=0
1058510600

1058610601
debugme echo "=== starting nntp STARTTLS dialog ==="
10587-
starttls_full_read '$^' '^20[01] ' && debugme echo "${debugpad}received server greeting" &&
10588-
starttls_just_send 'STARTTLS' && debugme echo "${debugpad}initiated STARTTLS" &&
10589-
starttls_full_read '$^' '^382 ' && debugme echo "${debugpad}received ack for STARTTLS"
10590-
local ret=$?
10602+
starttls_full_read '$^' '^20[01] ' '' "received server greeting" &&
10603+
starttls_just_send 'STARTTLS' "initiated STARTTLS" &&
10604+
starttls_full_read '$^' '^382 ' '' "received ack for STARTTLS"
10605+
ret=$?
1059110606
debugme echo "=== finished nntp STARTTLS dialog with ${ret} ==="
1059210607
return $ret
1059310608
}
1059410609

1059510610
starttls_postgres_dialog() {
10611+
local -i ret=0
10612+
local debugpad=" > "
10613+
local starttls_init=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F"
10614+
1059610615
debugme echo "=== starting postgres STARTTLS dialog ==="
10597-
local init_tls=", x00, x00 ,x00 ,x08 ,x04 ,xD2 ,x16 ,x2F"
10598-
socksend "${init_tls}" 0 && debugme echo "${debugpad}initiated STARTTLS" &&
10616+
socksend "${starttls_init}" 0 && debugme echo "${debugpad}initiated STARTTLS" &&
1059910617
starttls_io "" S 1 && debugme echo "${debugpad}received ack (="S") for STARTTLS"
10600-
local ret=$?
10618+
ret=$?
1060110619
debugme echo "=== finished postgres STARTTLS dialog with ${ret} ==="
1060210620
return $ret
1060310621
}
1060410622

1060510623
starttls_mysql_dialog() {
1060610624
local debugpad=" > "
10607-
local login_request="
10625+
local -i ret=0
10626+
local starttls_init="
1060810627
, x20, x00, x00, x01, # payload_length, sequence_id
1060910628
x85, xae, xff, x00, # capability flags, CLIENT_SSL always set
1061010629
x00, x00, x00, x01, # max-packet size
@@ -10614,16 +10633,16 @@ starttls_mysql_dialog() {
1061410633
x00, x00, x00, x00, x00, x00, x00"
1061510634

1061610635
debugme echo "=== starting mysql STARTTLS dialog ==="
10617-
socksend "${login_request}" 0
10618-
starttls_just_read 1 && debugme echo "${debugpad}read succeeded"
10636+
socksend "${starttls_init}" 0 && debugme echo "${debugpad}initiated STARTTLS" &&
10637+
starttls_just_read 1 "read succeeded"
1061910638
# 1 is the timeout value which only MySQL needs. Note, there seems no response whether STARTTLS
1062010639
# succeeded. We could try harder, see https://github.com/openssl/openssl/blob/master/apps/s_client.c
1062110640
# but atm this seems sufficient as later we will fail if there's no STARTTLS.
1062210641
# BUT: there seeem to be cases when the handshake fails (8S01Bad handshake --> 30 38 53 30 31 42 61 64 20 68 61 6e 64 73 68 61 6b 65).
1062310642
# also there's a banner in the reply "<version><somebytes>mysql_native_password"
1062410643
# TODO: We could detect if the server supports STARTTLS via the "Server Capabilities"
1062510644
# bit field, but we'd need to parse the binary stream, with greater precision than regex.
10626-
local ret=$?
10645+
ret=$?
1062710646
debugme echo "=== finished mysql STARTTLS dialog with ${ret} ==="
1062810647
return $ret
1062910648
}
@@ -10731,9 +10750,19 @@ fd_socket() {
1073110750
*) # we need to throw an error here -- otherwise testssl.sh treats the STARTTLS protocol as plain SSL/TLS which leads to FP
1073210751
fatal "FIXME: STARTTLS protocol $STARTTLS_PROTOCOL is not yet supported" $ERR_NOSUPPORT
1073310752
esac
10753+
ret=$?
10754+
case $ret in
10755+
0) return 0 ;;
10756+
3) fatal "No STARTTLS found in handshake" $ERR_CONNECT ;;
10757+
*) ((NR_STARTTLS_FAIL++))
10758+
# This are mostly timeouts here (code >=128). We give the client a chance to try again later. For cases
10759+
# where we have no STARTTLS in the server banner however - ret code=3 - we don't neet to try again
10760+
connectivity_problem $NR_STARTTLS_FAIL $MAX_STARTTLS_FAIL "STARTTLS handshake failed (code: $ret)" "repeated STARTTLS problems, giving up ($ret)"
10761+
return 6 ;;
10762+
esac
1073410763
fi
10764+
# Plain socket ok, yes or no?
1073510765
[[ $? -eq 0 ]] && return 0
10736-
prln_warning " STARTTLS handshake failed"
1073710766
return 1
1073810767
}
1073910768

0 commit comments

Comments
 (0)