mirror of
https://github.com/drwetter/testssl.sh.git
synced 2025-01-06 00:39:44 +01:00
WIP: Detecting "eTLS" 1.3 (Fix #1185)
This PR is an attempt at addressing #1185. According to https://www.etsi.org/deliver/etsi_ts/103500_103599/10352303/01.01.01_60/ts_10352303v010101p.pdf, if eTLS is in use, then the certificate should contain a subjectAltName extension with one or more "names" containing "visibility information." The "visibility information" is encoded as an otherName with a type-id of 0.4.0.3523.3.1 and a value of VisibilityInformation ::= SEQUENCE { fingerprint OCTET STRING (SIZE(10)), accessDescription UTF8String } The etsi_etls_visibility_info() function determines whether the certificate includes an "visibility information," and, if it does, extracts the fingerprints and access descriptions. This PR is a work-in-progress for two reasons. First, it has not been tested against any real certificates that contain "visibility information." Testing against real certificates would be helpful to verify that the parsing of the certificate is correct. Second, the presentation of the visibility information (both in the printed text and in what is sent to fileout()) may need improvement. Having seen no examples, it is not clear what the contents of accessDescription can be expected to look like. The document says that the contents will be "human-readable text," but it is not clear whether the description will be relatively short or very long.
This commit is contained in:
parent
7f8a0f2c8b
commit
30da1cdd72
150
testssl.sh
150
testssl.sh
@ -7289,6 +7289,152 @@ compare_server_name_to_cert() {
|
||||
return $subret
|
||||
}
|
||||
|
||||
# This function determines whether the certificate (arg3) contains "visibility
|
||||
# information" (see Section 4.3.3 of
|
||||
# https://www.etsi.org/deliver/etsi_ts/103500_103599/10352303/01.01.01_60/ts_10352303v010101p.pdf.
|
||||
etsi_etls_visibility_info() {
|
||||
local jsonID="$1"
|
||||
local spaces="$2"
|
||||
local cert="$3"
|
||||
local cert_txt="$4"
|
||||
local dercert tag
|
||||
local -a fingerprint=() access_description=()
|
||||
local -i i j len len1 len_name nr_visnames=0
|
||||
|
||||
# If "visibility information" is present, it will appear in the subjectAltName
|
||||
# extension (0603551D11) as an otherName with OID 0.4.0.3523.3.1 (060604009B430301).
|
||||
# OpenSSL displays all names of type otherName as "othername:<unsupported>".
|
||||
# As certificates will rarely include a name encoded as an otherName, check the
|
||||
# text version of the certificate for "othername:<unsupported>" before calling
|
||||
# external functions to obtain the DER encoded certficate.
|
||||
if [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\<unsupported\> ]]; then
|
||||
dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')"
|
||||
if [[ "$dercert" =~ 0603551D110101FF04[0-9A-F]*060604009B430301 ]] || \
|
||||
[[ "$dercert" =~ 0603551D1104[0-9A-F]*060604009B430301 ]]; then
|
||||
# Look for the beginning of the subjectAltName extension. It
|
||||
# will begin with the OID (2.5.29.17 = 0603551D11). After the OID
|
||||
# there may be an indication that the extension is critical (0101FF).
|
||||
# Finally will be the tag indicating that the value of the extension is
|
||||
# encoded as an OCTET STRING (04).
|
||||
if [[ "$dercert" =~ 0603551D110101FF04 ]]; then
|
||||
dercert="${dercert##*0603551D110101FF04}"
|
||||
else
|
||||
dercert="${dercert##*0603551D1104}"
|
||||
fi
|
||||
# Skip over the encoding of the length of the OCTET STRING.
|
||||
if [[ "${dercert:0:1}" == 8 ]]; then
|
||||
i="${dercert:1:1}"
|
||||
i=2*$i+2
|
||||
dercert="${dercert:i}"
|
||||
else
|
||||
dercert="${dercert:2}"
|
||||
fi
|
||||
# Next byte should be a 30 (SEQUENCE).
|
||||
if [[ "${dercert:0:2}" == 30 ]]; then
|
||||
# Get the length of the subjectAltName extension and then skip
|
||||
# over the encoding of the length.
|
||||
if [[ "${dercert:2:1}" == 8 ]]; then
|
||||
case "${dercert:3:1}" in
|
||||
1) len=2*0x${dercert:4:2}; dercert="${dercert:6}" ;;
|
||||
2) len=2*0x${dercert:4:4}; dercert="${dercert:8}" ;;
|
||||
3) len=2*0x${dercert:4:6}; dercert="${dercert:10}" ;;
|
||||
*) len=0 ;;
|
||||
esac
|
||||
else
|
||||
len=2*0x${dercert:2:2}
|
||||
dercert="${dercert:4}"
|
||||
fi
|
||||
if [[ $len -ne 0 ]] && [[ $len -lt ${#dercert} ]]; then
|
||||
# loop through all the names and extract the visibility information
|
||||
for (( i=0; i < len; i=i+len_name )); do
|
||||
tag="${dercert:i:2}"
|
||||
i+=2
|
||||
if [[ "${dercert:i:1}" == 8 ]]; then
|
||||
i+=1
|
||||
case "${dercert:i:1}" in
|
||||
1) i+=1; len_name=2*0x${dercert:i:2}; i+=2 ;;
|
||||
2) i+=1; len_name=2*0x${dercert:i:4}; i+=4 ;;
|
||||
3) i+=1; len_name=2*0x${dercert:i:6}; i+=4 ;;
|
||||
*) len=0 ;;
|
||||
esac
|
||||
else
|
||||
len_name=2*0x${dercert:i:2}
|
||||
i+=2
|
||||
fi
|
||||
[[ "$tag" == A0 ]] || continue
|
||||
# This is an otherName.
|
||||
[[ $len_name -gt 16 ]] || continue
|
||||
[[ "${dercert:i:16}" == 060604009B430301 ]] || continue
|
||||
# According to the OID, this is visibility information.
|
||||
j=$i+16
|
||||
# Skip over the tag (A0) and length for the otherName value.
|
||||
[[ "${dercert:j:2}" == A0 ]] || continue
|
||||
j+=2
|
||||
if [[ "${dercert:j:1}" == 8 ]]; then
|
||||
j+=1
|
||||
j+=2*0x${dercert:j:1}+1
|
||||
else
|
||||
j+=2
|
||||
fi
|
||||
# The value for this otherName is encoded as a SEQUENCE (30):
|
||||
# VisibilityInformation ::= SEQUENCE {
|
||||
# fingerprint OCTET STRING (SIZE(10)),
|
||||
# accessDescription UTF8String }
|
||||
[[ "${dercert:j:2}" == 30 ]] || continue
|
||||
j+=2
|
||||
if [[ "${dercert:j:1}" == 8 ]]; then
|
||||
j+=1
|
||||
case "${dercert:j:1}" in
|
||||
1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;;
|
||||
2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;;
|
||||
3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;;
|
||||
4) len1=0 ;;
|
||||
esac
|
||||
else
|
||||
len1=2*0x${dercert:j:2}
|
||||
j+=2
|
||||
fi
|
||||
[[ $len1 -ne 0 ]] || continue
|
||||
# Next is the 10-byte fingerprint, encoded as an OCTET STRING (04)
|
||||
[[ "${dercert:j:4}" == 040A ]] || continue
|
||||
j+=4
|
||||
fingerprint[nr_visnames]="$(asciihex_to_binary_file "${dercert:j:20}" "/dev/stdout")"
|
||||
j+=20
|
||||
# Finally comes the access description, encoded as a UTF8String (0C).
|
||||
[[ "${dercert:j:2}" == 0C ]] || continue
|
||||
j+=2
|
||||
if [[ "${dercert:j:1}" == "8" ]]; then
|
||||
j+=1
|
||||
case "${dercert:j:1}" in
|
||||
1) j+=1; len1=2*0x${dercert:j:2}; j+=2 ;;
|
||||
2) j+=1; len1=2*0x${dercert:j:4}; j+=4 ;;
|
||||
3) j+=1; len1=2*0x${dercert:j:6}; j+=6 ;;
|
||||
4) len1=0 ;;
|
||||
esac
|
||||
else
|
||||
len1=2*0x${dercert:j:2}
|
||||
j+=2
|
||||
fi
|
||||
access_description[nr_visnames]=""$(asciihex_to_binary_file "${dercert:j:len1}" "/dev/stdout")""
|
||||
nr_visnames+=1
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ $nr_visnames -eq 0 ]]; then
|
||||
outln "Not present"
|
||||
fileout "$jsonID" "INFO" "Not present"
|
||||
else
|
||||
for (( i=0; i < nr_visnames; i++ )); do
|
||||
[[ $i -ne 0 ]] && out "$spaces"
|
||||
outln "$(out_row_aligned_max_width "${fingerprint[i]} / ${access_description[i]}" "$spaces" $TERM_WIDTH)"
|
||||
fileout "$jsonID" "INFO" "${fingerprint[i]} / ${access_description[i]}"
|
||||
done
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# NOTE: arg3 must contain the text output of $HOSTCERT.
|
||||
must_staple() {
|
||||
local jsonID="cert_mustStapleExtension"
|
||||
@ -7999,6 +8145,10 @@ certificate_info() {
|
||||
# https://certs.opera.com/03/ev-oids.xml
|
||||
# see #967
|
||||
|
||||
out "$indent"; pr_bold " eTLS "
|
||||
jsonID="cert_eTLS"
|
||||
etsi_etls_visibility_info "$jsonID" "$spaces" "$HOSTCERT" "$cert_txt"
|
||||
|
||||
out "$indent"; pr_bold " Certificate Validity (UTC) "
|
||||
|
||||
# FreeBSD + OSX can't swallow the leading blank:
|
||||
|
Loading…
Reference in New Issue
Block a user