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:
+65
-62
@@ -2359,7 +2359,7 @@ hex2binary() {
|
||||
|
||||
# convert 414243 into ABC
|
||||
hex2ascii() {
|
||||
hex2binary $1
|
||||
hex2binary "$1"
|
||||
}
|
||||
|
||||
# arg1: text string
|
||||
@@ -22622,7 +22622,8 @@ get_caa_rrecord() {
|
||||
#
|
||||
get_https_rrecord() {
|
||||
local raw_https=""
|
||||
local hash="" len line=""
|
||||
local hash="" line=""
|
||||
local len # better fix as integer?
|
||||
local len_alpnID=""
|
||||
local alpnID=""
|
||||
local alpnID_wire=""
|
||||
@@ -22634,42 +22635,36 @@ get_https_rrecord() {
|
||||
[[ -n "$NODNS" ]] && return 2 # if minimum DNS lookup was instructed, leave here
|
||||
"$HAS_DIG_NOIDNOUT" && noidnout="+noidnout"
|
||||
|
||||
# There's a) 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.
|
||||
# On top of that b) there's also an extended format which e.g. cloudflare uses:
|
||||
# $ host -t type65 testssl.net
|
||||
# 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.
|
||||
# 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 new binaries only. Thus we try first
|
||||
# whether we can query the HTTPS records directly as this gives us that already everything we want in
|
||||
# in clear text and also we can avoid to parse the encoded formats.
|
||||
|
||||
# "tail -1" and the awk commands make sure we use the right lines when we encounter a CNAME
|
||||
OPENSSL_CONF=""
|
||||
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
|
||||
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
|
||||
text_httpsrr=$(host -t HTTPS $1 2>/dev/null)
|
||||
text_httpsrr=${text_httpsrr#*record }
|
||||
elif "$HAS_NSLOOKUP_HTTPS"; then # from 4th field onwards \/
|
||||
text_httpsrr=$(nslookup -type=HTTPS $1 | awk '/'"^${1}"'.*rdata_65// { print substr($0,index($0,$4)) }')
|
||||
text_httpsrr="$(host -t HTTPS "$1" 2>/dev/null | awk -F'HTTP service bindings ' '/HTTP service bindings /{print $2}')"
|
||||
elif "$HAS_NSLOOKUP_HTTPS"; then
|
||||
text_httpsrr="$(nslookup -type=HTTPS "$1" | awk -F'rdata_65 = ' '/rdata_65 =/{print $2}' )"
|
||||
fi
|
||||
|
||||
if [[ -n "$text_httpsrr" ]]; then
|
||||
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
|
||||
fi
|
||||
|
||||
# Now we need to try parsing the raw output
|
||||
# Format probably: https://www.rfc-editor.org/rfc/rfc3597 (plus updates)
|
||||
|
||||
# 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
|
||||
# As we didn't succeed yet, we need to try parsing the raw output. First is to get the TYPE65 record
|
||||
# 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
|
||||
|
||||
# for host:
|
||||
# 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)"
|
||||
# empty if there's no such record
|
||||
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
|
||||
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
|
||||
raw_https=""
|
||||
else
|
||||
@@ -22698,76 +22693,82 @@ get_https_rrecord() {
|
||||
raw_https="${raw_https/$1 has TYPE65 record /}"
|
||||
fi
|
||||
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
|
||||
else
|
||||
return 1
|
||||
# No dig, drill, host, or nslookup --> complaint was elsewhere already
|
||||
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.
|
||||
# Format is like: https://www.rfc-editor.org/rfc/rfc3597 (plus updates)
|
||||
|
||||
# dev.testssl.sh:
|
||||
# 1 . alpn="h2" port=443 ipv6hint=2a01:238:4308:a920:1000:0:b:1337
|
||||
#
|
||||
# 36 000100000100030268320003000201BB000600102A0102384308A920 10000000000B1337
|
||||
# alpn| L h 2 443 2a010238... L=len
|
||||
# TL alpn| L h 2 443 2a010238... L=len of alpn entries, TL=total length of the following by, excluding spaces
|
||||
#
|
||||
# -----------------
|
||||
# testssl.net (split over a couple of lines)
|
||||
# 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
|
||||
# alpn| L h 3 L h 2 |IPv4#1||IPv4#2|
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# Now comes the last straw, decoding og the stream. It only works for short entries like 1. alpn=h2,h3
|
||||
# Be aware that all variables are strings here! Therefore we use double quotes so that e.g. 03 won't become 3
|
||||
# For now the following only works for short entries like 1. alpn=h2,h3
|
||||
if [[ -z "$raw_https" ]]; then
|
||||
return 1
|
||||
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
|
||||
# \# 36 000100000100030268320003000201BB000600102A01023842816755 10000000000B1337
|
||||
[[ $DEBUG -eq 1 ]] && echo "$hash $len $line"
|
||||
|
||||
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
|
||||
if [[ $svc_priority == 1 ]]; then
|
||||
if [[ $svc_priority == "1" ]]; then
|
||||
# mock text representation
|
||||
svc_priority="$svc_priority . "
|
||||
svc_priority+=" . "
|
||||
alpnID="${alpnID}${svc_priority}"
|
||||
fi
|
||||
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
|
||||
if [[ ${line:8:2} == "01" ]]; then # Then comes SvcParamKeys, see rfc 14.3.2 which should be alpn=1
|
||||
alpnID+="\"" # double quote for clear text
|
||||
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?
|
||||
len_alpnID=${line:12:2} # length of alpn entries (1st?)
|
||||
alpnID_wire=${line:16:4} # value of first entry
|
||||
alpnID=${alpnID}$(hex2ascii $alpnID_wire)
|
||||
# from here it only works for one simple entry (like h2 or h2,h3)
|
||||
if [[ "$len_alpnID" != "03" ]]; 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="${alpnID},$(hex2ascii $alpnID_wire)"
|
||||
len_alpnID="${line:12:2}" # length of alpn entries, e.g. 03 or 06
|
||||
alpnID_wire="${line:16:4}" # value of first entry
|
||||
alpnID="${alpnID}$(hex2ascii "$alpnID_wire")"
|
||||
localPTR=20
|
||||
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="${alpnID},$(hex2ascii "$alpnID_wire")"
|
||||
localPTR=26
|
||||
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
|
||||
if [[ $len -eq 10 ]]; then
|
||||
[[ $DEBUG -eq 1 ]] && echo "end 10"
|
||||
break
|
||||
fi
|
||||
# done if localPTR / 2 = len or localPTR not empty
|
||||
echo "key: ${line:localPTR:4}"
|
||||
# key=1: alpn
|
||||
# key=3: port
|
||||
# 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
|
||||
out "please report unknown HTTPS RR $line with flag @ $NODE"
|
||||
return 7
|
||||
fi
|
||||
done <<< "$raw_https"
|
||||
safe_echo "$alpnID"
|
||||
fi
|
||||
return 0
|
||||
@@ -23602,6 +23603,7 @@ dns_https_rr () {
|
||||
local jsonID="DNS_HTTPS_rrecord"
|
||||
local https_rr=""
|
||||
local indent=""
|
||||
local https_rr_node="$NODE"
|
||||
|
||||
out "$indent"; pr_bold " DNS HTTPS RR"; out " (expt.): "
|
||||
if [[ -n "$NODNS" ]]; then
|
||||
@@ -23611,7 +23613,9 @@ dns_https_rr () {
|
||||
out "(instructed to use the proxy for DNS only)"
|
||||
fileout "${jsonID}" "INFO" "check skipped as instructed (proxy)"
|
||||
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
|
||||
pr_svrty_good "yes" ; out ": "
|
||||
prln_italic "$(out_row_aligned_max_width "$https_rr" "$indent " $TERM_WIDTH)"
|
||||
@@ -23624,7 +23628,6 @@ dns_https_rr () {
|
||||
}
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
# but can be extended in the future. An array might be more handy
|
||||
|
||||
Reference in New Issue
Block a user