mirror of
https://github.com/drwetter/testssl.sh.git
synced 2026-06-02 22:48:49 +02:00
Updatesr get_https_rrecord()
- quote vars (hoping it'll resolve the Mac runner issue) - make sure CNAMEs are properly parsed - end get_https_rrecord() earlier when there's no record but DNS binaries are "HTTPS record aware" - while loop was redundant - better comments Elsewhere: make sure get_https_rrecord is called with a trailing dot for the NODE
This commit is contained in:
+76
-73
@@ -2359,7 +2359,7 @@ hex2binary() {
|
|||||||
|
|
||||||
# convert 414243 into ABC
|
# convert 414243 into ABC
|
||||||
hex2ascii() {
|
hex2ascii() {
|
||||||
hex2binary $1
|
hex2binary "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# arg1: text string
|
# arg1: text string
|
||||||
@@ -22622,7 +22622,8 @@ get_caa_rrecord() {
|
|||||||
#
|
#
|
||||||
get_https_rrecord() {
|
get_https_rrecord() {
|
||||||
local raw_https=""
|
local raw_https=""
|
||||||
local hash="" len line=""
|
local hash="" line=""
|
||||||
|
local len # better fix as integer?
|
||||||
local len_alpnID=""
|
local len_alpnID=""
|
||||||
local alpnID=""
|
local alpnID=""
|
||||||
local alpnID_wire=""
|
local alpnID_wire=""
|
||||||
@@ -22634,42 +22635,36 @@ get_https_rrecord() {
|
|||||||
[[ -n "$NODNS" ]] && return 2 # if minimum DNS lookup was instructed, leave here
|
[[ -n "$NODNS" ]] && return 2 # if minimum DNS lookup was instructed, leave here
|
||||||
"$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
|
"$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
|
||||||
|
|
||||||
# There's a) the possibility to query HTTPS RR records directly like "dig +short HTTPS dev.testssl.sh",
|
# There's the possibility to query HTTPS RR records directly like "dig +short HTTPS dev.testssl.sh",
|
||||||
# "drill HTTPS FQDN" or "nslookup -type=HTTPS FQDN". This works for newer binaries only, unfortunately.
|
# "drill HTTPS FQDN" or "nslookup -type=HTTPS FQDN". This works for new binaries only. Thus we try first
|
||||||
# On top of that b) there's also an extended format which e.g. cloudflare uses:
|
# whether we can query the HTTPS records directly as this gives us that already everything we want in
|
||||||
# $ host -t type65 testssl.net
|
# in clear text and also we can avoid to parse the encoded formats.
|
||||||
# testssl.net has TYPE65 record \# 136 00010000010006026833026832000400086815229AAC43CDE7000500 470045FE0D0041A70020002057F87361C7B5A3B8CD3C028892690D35 2863623DAD4E03D33B231A4C3C8BB02B0004000100010012636C6F75 64666C6172652D6563682E636F6D0000000600202606470030310000 00000000AC43CDE72606470030360000000000006815229A
|
|
||||||
# $ host -t HTTPS testssl.net
|
|
||||||
# testssl.net has HTTPS record 1 . alpn="h3,h2" ipv4hint=104.21.34.154,172.67.205.231 ech=AEX+DQBBpwAgACBX+HNhx7WjuM08AoiSaQ01KGNiPa1OA9M7IxpMPIuwKwAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:3031::ac43:cde7,2606:4700:3036::6815:229a
|
|
||||||
# ECH is the encrypted client hello --> for esni (https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)
|
|
||||||
# Nice description: https://www.netmeister.org/blog/https-rrs.html
|
|
||||||
|
|
||||||
# Thus we try first whether we can query the HTTPS records directly as this gives us that already
|
|
||||||
# in clear text and also we can avoid to parse the encoded format. We'll do that as a fallback but
|
|
||||||
# at this moment we're trying to scrape only the values alpn from it, if they come first.
|
|
||||||
|
|
||||||
|
# "tail -1" and the awk commands make sure we use the right lines when we encounter a CNAME
|
||||||
OPENSSL_CONF=""
|
OPENSSL_CONF=""
|
||||||
if "$HAS_DIG_HTTPS"; then
|
if "$HAS_DIG_HTTPS"; then
|
||||||
text_httpsrr=$(dig +short +search +timeout=3 +tries=3 $noidnout HTTPS "$1" 2>/dev/null)
|
text_httpsrr="$(dig +short +search +timeout=3 +tries=3 $noidnout HTTPS "$1" 2>/dev/null | tail -1)"
|
||||||
elif "$HAS_DRILL_HTTPS"; then
|
elif "$HAS_DRILL_HTTPS"; then
|
||||||
text_httpsrr=$(drill -Q HTTPS $1 2>/dev/null)
|
text_httpsrr="$(drill -Q HTTPS "$1" 2>/dev/null | tail -1)"
|
||||||
elif "$HAS_HOST_HTTPS"; then
|
elif "$HAS_HOST_HTTPS"; then
|
||||||
text_httpsrr=$(host -t HTTPS $1 2>/dev/null)
|
text_httpsrr="$(host -t HTTPS "$1" 2>/dev/null | awk -F'HTTP service bindings ' '/HTTP service bindings /{print $2}')"
|
||||||
text_httpsrr=${text_httpsrr#*record }
|
elif "$HAS_NSLOOKUP_HTTPS"; then
|
||||||
elif "$HAS_NSLOOKUP_HTTPS"; then # from 4th field onwards \/
|
text_httpsrr="$(nslookup -type=HTTPS "$1" | awk -F'rdata_65 = ' '/rdata_65 =/{print $2}' )"
|
||||||
text_httpsrr=$(nslookup -type=HTTPS $1 | awk '/'"^${1}"'.*rdata_65// { print substr($0,index($0,$4)) }')
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$text_httpsrr" ]]; then
|
if [[ -n "$text_httpsrr" ]]; then
|
||||||
safe_echo "$text_httpsrr"
|
safe_echo "$text_httpsrr"
|
||||||
|
OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134
|
||||||
|
return 0
|
||||||
|
elif "$HAS_DIG_HTTPS" || "$HAS_DRILL_HTTPS" || "$HAS_HOST_HTTPS" || "$HAS_NSLOOKUP_HTTPS"; then
|
||||||
|
# no record despite binaries are "HTTPS record aware"
|
||||||
|
OPENSSL_CONF="$saved_openssl_conf"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Now we need to try parsing the raw output
|
# As we didn't succeed yet, we need to try parsing the raw output. First is to get the TYPE65 record
|
||||||
# Format probably: https://www.rfc-editor.org/rfc/rfc3597 (plus updates)
|
# as text. These days (2026) it's not that common anymore. Mac is the party pooper as it normally returns
|
||||||
|
# a hex stream only --in 2026. Here's how output of old+ancient client DNS binaries may look like with TYPE65
|
||||||
# If there's a type65 record there are 2x3 output formats, mostly depending on age of distribution
|
|
||||||
# -- roughly that's the difference between text and binary format -- and the type of DNS client
|
|
||||||
|
|
||||||
# for host:
|
# for host:
|
||||||
# 1) 'google.com has HTTPS record 1 . alpn="h2,h3" '
|
# 1) 'google.com has HTTPS record 1 . alpn="h2,h3" '
|
||||||
@@ -22687,10 +22682,10 @@ get_https_rrecord() {
|
|||||||
raw_https="$(dig $DIG_R +short +search +timeout=3 +tries=3 $noidnout type65 "$1" 2>/dev/null)"
|
raw_https="$(dig $DIG_R +short +search +timeout=3 +tries=3 $noidnout type65 "$1" 2>/dev/null)"
|
||||||
# empty if there's no such record
|
# empty if there's no such record
|
||||||
elif "$HAS_DRILL"; then
|
elif "$HAS_DRILL"; then
|
||||||
raw_https="$(drill $1 type65 | grep -v '^;;' | awk '/'"^${1}"'.*TYPE65/ { print substr($0,index($0,$5)) }' )" # from 5th field onwards
|
raw_https="$(drill "$1" type65 | grep -v '^;;' | awk '/'"^${1}"'.*TYPE65/ { print substr($0,index($0,$5)) }' )" # from 5th field onwards
|
||||||
# empty if there's no such record
|
# empty if there's no such record
|
||||||
elif "$HAS_HOST"; then
|
elif "$HAS_HOST"; then
|
||||||
raw_https="$(host -t type65 $1)"
|
raw_https="$(host -t type65 "$1")"
|
||||||
if [[ "$raw_https" =~ "has no HTTPS|has no TYPE65" ]]; then
|
if [[ "$raw_https" =~ "has no HTTPS|has no TYPE65" ]]; then
|
||||||
raw_https=""
|
raw_https=""
|
||||||
else
|
else
|
||||||
@@ -22698,76 +22693,82 @@ get_https_rrecord() {
|
|||||||
raw_https="${raw_https/$1 has TYPE65 record /}"
|
raw_https="${raw_https/$1 has TYPE65 record /}"
|
||||||
fi
|
fi
|
||||||
elif "$HAS_NSLOOKUP"; then
|
elif "$HAS_NSLOOKUP"; then
|
||||||
raw_https="$(strip_lf "$(nslookup -type=type65 $1 | awk '/'"^${1}"'.*rdata_65/ { print substr($0,index($0,$4)) }' )")"
|
raw_https="$(strip_lf "$(nslookup -type=type65 "$1" | awk '/'"^${1}"'.*rdata_65/ { print substr($0,index($0,$4)) }' )")"
|
||||||
# empty if there's no such record
|
# empty if there's no such record
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
# No dig, drill, host, or nslookup --> complaint was elsewhere already
|
# No dig, drill, host, or nslookup --> complaint was elsewhere already
|
||||||
fi
|
fi
|
||||||
OPENSSL_CONF="$saved_openssl_conf" # see https://github.com/drwetter/testssl.sh/issues/134
|
OPENSSL_CONF="$saved_openssl_conf" # We're done now with openssl, see https://github.com/drwetter/testssl.sh/issues/134
|
||||||
|
|
||||||
# dig +short HTTPS dev.testssl.sh / dig +short type65 dev.testssl.sh
|
# Now comes the third, more tricky part and the last straw --> parsing the hex stream which was returned if it was returned.
|
||||||
# 1 . alpn="h2" port=443 ipv6hint=2a01:238:4308:a920:1000:0:b:1337
|
# Format is like: https://www.rfc-editor.org/rfc/rfc3597 (plus updates)
|
||||||
#
|
|
||||||
# 36 000100000100030268320003000201BB000600102A0102384308A920 10000000000B1337
|
|
||||||
# alpn| L h 2 443 2a010238... L=len
|
|
||||||
#
|
|
||||||
# -----------------
|
|
||||||
# testssl.net (split over a couple of lines)
|
|
||||||
#
|
|
||||||
# 1. alpn="h3,h2" ipv4hint=104.21.34.154,172.67.205.231
|
|
||||||
# 136 00010000010006026833026832000400086815229AAC43CDE7000500 470045FE0D0041F3002000202BD0935ED66980C1862F2570C0D6014D
|
|
||||||
# alpn| L h 3 L h 2 |IPv4#1||IPv4#2|
|
|
||||||
|
|
||||||
# ech=AEX+DQBBzgAgACBQGA9EFbz+PkJAXSXtcqJluxLlhxIgzhJ+GhTtRd4nJQAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:3031::ac43:cde7,2606:4700:3036::6815:229a
|
# dev.testssl.sh:
|
||||||
# 733A7CFAAEA5E4DD9CA43D4C24199E330004000100010012636C6F75 64666C6172652D6563682E636F6D0000000600202606470030310000 00000000AC43CDE72606470030360000000000006815229A
|
# 1 . alpn="h2" port=443 ipv6hint=2a01:238:4308:a920:1000:0:b:1337
|
||||||
# | cloudflare-ech.com | IPv6#1 #IPv6#2
|
#
|
||||||
|
# 36 000100000100030268320003000201BB000600102A0102384308A920 10000000000B1337
|
||||||
|
# TL alpn| L h 2 443 2a010238... L=len of alpn entries, TL=total length of the following by, excluding spaces
|
||||||
|
#
|
||||||
|
# -----------------
|
||||||
|
# testssl.net (here hown over a couple of lines):
|
||||||
|
# 1 . alpn="h3,h2" ipv4hint=104.21.34.154,172.67.205.231 ech=AEX+DQBBpwAgACBX+HNhx7WjuM08AoiSaQ01KGNiPa1OA9M7IxpMPIuwKwAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:3031::ac43:cde7,2606:4700:3036::6815:229a
|
||||||
|
# ECH is the encrypted client hello --> for esni (https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)
|
||||||
|
# Nice description: https://www.netmeister.org/blog/https-rrs.html
|
||||||
|
#
|
||||||
|
# 1. alpn="h3,h2" ipv4hint=104.21.34.154,172.67.205.231
|
||||||
|
# 136 00010000010006026833026832000400086815229AAC43CDE7000500 470045FE0D0041F3002000202BD0935ED66980C1862F2570C0D6014D
|
||||||
|
# TL alpn| L h 3 L h 2 |IPv4#1||IPv4#2|
|
||||||
|
#
|
||||||
|
# ech=AEX+DQBBzgAgACBQGA9EFbz+PkJAXSXtcqJluxLlhxIgzhJ+GhTtRd4nJQAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA= ipv6hint=2606:4700:3031::ac43:cde7,2606:4700:3036::6815:229a
|
||||||
|
# 733A7CFAAEA5E4DD9CA43D4C24199E330004000100010012636C6F75 64666C6172652D6563682E636F6D0000000600202606470030310000 00000000AC43CDE72606470030360000000000006815229A
|
||||||
|
# | cloudflare-ech.com | IPv6#1 #IPv6#2
|
||||||
|
|
||||||
|
# Be aware that all variables are strings here! Therefore we use double quotes so that e.g. 03 won't become 3
|
||||||
# Now comes the last straw, decoding og the stream. It only works for short entries like 1. alpn=h2,h3
|
# For now the following only works for short entries like 1. alpn=h2,h3
|
||||||
if [[ -z "$raw_https" ]]; then
|
if [[ -z "$raw_https" ]]; then
|
||||||
return 1
|
return 1
|
||||||
elif [[ "$raw_https" =~ \#\ [0-9][0-9] ]]; then
|
elif [[ "$raw_https" =~ \#\ [0-9][0-9] ]]; then
|
||||||
while read hash len line ;do
|
# signals that we're on the right track with type65 interpretation
|
||||||
|
read hash len line <<< "$raw_https"
|
||||||
# \# 10 00010000010003026832
|
# \# 10 00010000010003026832
|
||||||
# \# 36 000100000100030268320003000201BB000600102A01023842816755 10000000000B1337
|
# \# 36 000100000100030268320003000201BB000600102A01023842816755 10000000000B1337
|
||||||
[[ $DEBUG -eq 1 ]] && echo "$hash $len $line"
|
[[ $DEBUG -eq 1 ]] && echo "$hash $len $line"
|
||||||
|
|
||||||
if [[ "${line:0:4}" == 0001 ]]; then # marker to proceed, belongs to SvcPriority, see rfc9460, 2.4.3
|
if [[ "${line:0:4}" == 0001 ]]; then # marker to proceed, belongs to SvcPriority, see rfc9460, 2.4.3
|
||||||
svc_priority=$(printf "%0d" "$((10#${line:2:2}))") # 1 is most often, 0 is alias
|
svc_priority=$(printf "%0d" "$((10#${line:2:2}))") # 1 is most often, 0 is alias
|
||||||
if [[ $svc_priority == 1 ]]; then
|
if [[ $svc_priority == "1" ]]; then
|
||||||
# mock text representation
|
# mock text representation
|
||||||
svc_priority="$svc_priority . "
|
svc_priority+=" . "
|
||||||
alpnID="${alpnID}${svc_priority}"
|
alpnID="${alpnID}${svc_priority}"
|
||||||
fi
|
fi
|
||||||
if [[ ${line:8:2} == 01 ]]; then # Then comes SvcParamKeys, see rfc 14.3.2 which should be alpn=1
|
if [[ ${line:8:2} == "01" ]]; then # Then comes SvcParamKeys, see rfc 14.3.2 which should be alpn=1
|
||||||
alpnID="${alpnID}alpn=\"" # double quote for clear text
|
alpnID+="\"" # double quote for clear text
|
||||||
else
|
else
|
||||||
continue # If the 1st element is not alpn, next iteration of loop will fail.
|
continue # If the 1st element is not alpn, next iteration of loop will fail for now
|
||||||
fi # Should we care as SvcParamKey!=alpn doesn't seems not very common?
|
fi # Should we care as SvcParamKey!=alpn doesn't seems not very common?
|
||||||
len_alpnID=${line:12:2} # length of alpn entries (1st?)
|
len_alpnID="${line:12:2}" # length of alpn entries, e.g. 03 or 06
|
||||||
alpnID_wire=${line:16:4} # value of first entry
|
alpnID_wire="${line:16:4}" # value of first entry
|
||||||
alpnID=${alpnID}$(hex2ascii $alpnID_wire)
|
alpnID="${alpnID}$(hex2ascii "$alpnID_wire")"
|
||||||
# from here it only works for one simple entry (like h2 or h2,h3)
|
localPTR=20
|
||||||
if [[ "$len_alpnID" != "03" ]]; then # 06 would be another entry e.g. h3, quote the rhs!
|
if [[ "$len_alpnID" == "06" ]]; then # 06 would be another entry e.g. h3, quote the rhs!
|
||||||
alpnID_wire=${line:22:4} #FIXME: we can't cope with three entries yet
|
alpnID_wire="${line:22:4}" #FIXME: we can't cope with three entries yet
|
||||||
alpnID="${alpnID},$(hex2ascii $alpnID_wire)"
|
alpnID="${alpnID},$(hex2ascii "$alpnID_wire")"
|
||||||
|
localPTR=26
|
||||||
fi
|
fi
|
||||||
[[ ${line:8:2} == 01 ]] && alpnID="${alpnID}\"" # if alpn add trailing double quote
|
[[ ${line:8:2} == "01" ]] && alpnID+="\"" # if alpn and we're done add trailing double quote
|
||||||
|
|
||||||
# best is to check len and then stop now
|
# done if localPTR / 2 = len or localPTR not empty
|
||||||
if [[ $len -eq 10 ]]; then
|
echo "key: ${line:localPTR:4}"
|
||||||
[[ $DEBUG -eq 1 ]] && echo "end 10"
|
# key=1: alpn
|
||||||
break
|
# key=3: port
|
||||||
fi
|
# key=4: IPv4
|
||||||
|
# key=6: IPv6
|
||||||
|
|
||||||
# len_alpnID=$((len_alpnID*2)) # =>word! Now get name from 4th and value from 4th+len position...
|
|
||||||
# alpnID="$(hex2ascii ${line:4:$len_alpnID})"
|
|
||||||
# alpnID_wire="$(hex2ascii "${line:$((4+len_alpnID)):100}")"
|
|
||||||
else
|
else
|
||||||
out "please report unknown HTTPS RR $line with flag @ $NODE"
|
out "please report unknown HTTPS RR $line with flag @ $NODE"
|
||||||
return 7
|
return 7
|
||||||
fi
|
fi
|
||||||
done <<< "$raw_https"
|
|
||||||
safe_echo "$alpnID"
|
safe_echo "$alpnID"
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
@@ -23602,6 +23603,7 @@ dns_https_rr () {
|
|||||||
local jsonID="DNS_HTTPS_rrecord"
|
local jsonID="DNS_HTTPS_rrecord"
|
||||||
local https_rr=""
|
local https_rr=""
|
||||||
local indent=""
|
local indent=""
|
||||||
|
local https_rr_node="$NODE"
|
||||||
|
|
||||||
out "$indent"; pr_bold " DNS HTTPS RR"; out " (expt.): "
|
out "$indent"; pr_bold " DNS HTTPS RR"; out " (expt.): "
|
||||||
if [[ -n "$NODNS" ]]; then
|
if [[ -n "$NODNS" ]]; then
|
||||||
@@ -23611,9 +23613,11 @@ dns_https_rr () {
|
|||||||
out "(instructed to use the proxy for DNS only)"
|
out "(instructed to use the proxy for DNS only)"
|
||||||
fileout "${jsonID}" "INFO" "check skipped as instructed (proxy)"
|
fileout "${jsonID}" "INFO" "check skipped as instructed (proxy)"
|
||||||
else
|
else
|
||||||
https_rr="$(get_https_rrecord $NODE)"
|
# append a dot if there was none
|
||||||
|
[[ $https_rr_node =~ '.'$ ]] || https_rr_node+="."
|
||||||
|
https_rr="$(get_https_rrecord $https_rr_node)"
|
||||||
if [[ -n "$https_rr" ]]; then
|
if [[ -n "$https_rr" ]]; then
|
||||||
pr_svrty_good "yes" ; out ": "
|
pr_svrty_good "yes" ; out ": "
|
||||||
prln_italic "$(out_row_aligned_max_width "$https_rr" "$indent " $TERM_WIDTH)"
|
prln_italic "$(out_row_aligned_max_width "$https_rr" "$indent " $TERM_WIDTH)"
|
||||||
fileout "${jsonID}" "OK" "$https_rr"
|
fileout "${jsonID}" "OK" "$https_rr"
|
||||||
else
|
else
|
||||||
@@ -23624,7 +23628,6 @@ dns_https_rr () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Check messages which needed to be processed. I.e. those which would have destroyed the nice
|
# Check messages which needed to be processed. I.e. those which would have destroyed the nice
|
||||||
# screen output and thus havve been postponed. This is just an idea and is only used once
|
# screen output and thus havve been postponed. This is just an idea and is only used once
|
||||||
# but can be extended in the future. An array might be more handy
|
# but can be extended in the future. An array might be more handy
|
||||||
|
|||||||
Reference in New Issue
Block a user