mirror of
https://github.com/drwetter/testssl.sh.git
synced 2024-12-31 22:09:44 +01:00
Name check for XMPP servers
This PR is an attempt to address #1097. I have only been able to test it against jabber.topf.org and against locally created test certificates, so it needs more testing. The main issue that this addresses is that testssl.sh currently checks against the wrong name for XMPP servers. According to RFC 6120, Section 13.7.2.1: o The initiating entity sets the source domain of its reference identifiers to the 'to' address it communicates in the initial stream header; i.e., this is the identity it expects the receiving entity to provide in a PKIX certificate. So, if the --xmpphost option is provided, then the name provided in that option should be compared against the name in the certificate rather than the host name. compare_server_name_to_cert() currently takes the server name to look for in the certificate as an parameter, but every call to compare_server_name_to_cert() uses $NODE as the argument. So, this PR removes the parameter and sets $servername to either $XMPP_HOST or $NODE as appropriate. This small change alone should fix the problem for most XMPP servers since the server's name SHOULD appear in the certificate encoded as a DNS name. That is the case for the one server I could test - jabber.topf.org. The majority of the code in this PR is to address the other possibilities noted in RFC 6120, Section 13.7.1.2.1. This section notes that an XMPP server's identity name also appear in the subjectAltName extension as an otherName, either an SRV-ID or an XmppAddr identifier. Unfortunately, OpenSSL's certificate printer does not support otherName and just prints "othername:<unsupported>". So, this PR includes code to manually extract any SRV-ID or XmppAddr names from the certificate. This involves parsing the DER encoding of the certificate to look for the subjectAltName extension, looping through all of the names in the extension, and pulling out the names of these two name forms. This code is only run if the server is an XMPP server and the certificate does not have a matching DNS name. So, this code will rarely be executed. This PR addresses one other issue. There is code in certificate_info() to set the variables $has_dns_sans and $has_dns_sans_nosni. These variables are needed to address the following requirement: # Find out if the subjectAltName extension is present and contains # a DNS name, since Section 6.3 of RFC 6125 says: # Security Warning: A client MUST NOT seek a match for a reference # identifier of CN-ID if the presented identifiers include a DNS-ID, # SRV-ID, URI-ID, or any application-specific identifier types # supported by the client. While it is relatively easy to determine whether a certificate includes a DNS name in the subjectAltName extension, as noted above, it is not easy to determine whether it has an SRV-ID or an XmppAddr. So, this PR leverages the work compare_server_name_to_cert() does in parsing the subjectAltName extension by having compare_server_name_to_cert() set a global variable indicating whether the certificate has a subjectAltName extension with a relevant name form (DNS, SRV-ID, or XmppAddr for XMPP, or DNS for other servers). $has_dns_sans and $has_dns_sans_nosni are then just set to the value of this global variable.
This commit is contained in:
parent
1b52834dfc
commit
e0f5c7513a
140
testssl.sh
140
testssl.sh
@ -255,6 +255,7 @@ GOOD_CA_BUNDLE="" # A bundle of CA certificates that can b
|
||||
CERTIFICATE_LIST_ORDERING_PROBLEM=false # Set to true if server sends a certificate list that contains a certificate
|
||||
# that does not certify the one immediately preceding it. (See RFC 8446, Section 4.4.2)
|
||||
STAPLED_OCSP_RESPONSE=""
|
||||
HAS_DNS_SANS=false # Whether the certificate includes a subjectAltName extension with a DNS name or an application-specific identifier type.
|
||||
MEASURE_TIME_FILE=${MEASURE_TIME_FILE:-""}
|
||||
if [[ -n "$MEASURE_TIME_FILE" ]] && [[ -z "$MEASURE_TIME" ]]; then
|
||||
MEASURE_TIME=true
|
||||
@ -6965,15 +6966,30 @@ wildcard_match()
|
||||
# 10, if the server name provided is a wildcard match against the CN AND a name in the SAN
|
||||
|
||||
compare_server_name_to_cert() {
|
||||
local servername="$(toupper "$1")"
|
||||
local cert="$2"
|
||||
local cn dns_sans ip_sans san
|
||||
local cert="$1"
|
||||
local servername cn dns_sans ip_sans san dercert tag
|
||||
local srv_id="" xmppaddr=""
|
||||
local -i i len len1
|
||||
local -i subret=0 # no error condition, passing results
|
||||
|
||||
HAS_DNS_SANS=false
|
||||
if [[ -n "$XMPP_HOST" ]]; then
|
||||
# RFC 6120, Section 13.7.2.1, states that for XMPP the identity that
|
||||
# should appear in the server's certificate is identity that appears
|
||||
# in the the 'to' address that the client communicates in the initial
|
||||
# stream header.
|
||||
servername="$(toupper "$XMPP_HOST")"
|
||||
else
|
||||
servername="$(toupper "$NODE")"
|
||||
fi
|
||||
|
||||
# Check whether any of the DNS names in the certificate match the servername
|
||||
dns_sans="$(get_san_dns_from_cert "$cert")"
|
||||
while read san; do
|
||||
[[ -n "$san" ]] && [[ $(toupper "$san") == "$servername" ]] && subret=1 && break
|
||||
if [[ -n "$san" ]]; then
|
||||
HAS_DNS_SANS=true
|
||||
[[ $(toupper "$san") == "$servername" ]] && subret=1 && break
|
||||
fi
|
||||
done <<< "$dns_sans"
|
||||
|
||||
if [[ $subret -eq 0 ]]; then
|
||||
@ -6984,6 +7000,109 @@ compare_server_name_to_cert() {
|
||||
[[ -n "$san" ]] && [[ "$san" == "$servername" ]] && subret=1 && break
|
||||
done <<< "$ip_sans"
|
||||
fi
|
||||
|
||||
if [[ $subret -eq 0 ]] && [[ -n "$XMPP_HOST" ]]; then
|
||||
# For XMPP hosts, in addition to checking for a matching DNS name,
|
||||
# should also check for a matching SRV-ID or XmppAddr identifier.
|
||||
dercert="$($OPENSSL x509 -in "$cert" -outform DER 2>>$ERRFILE | hexdump -v -e '16/1 "%02X"')"
|
||||
dercert="${dercert##*0603551D1104}"
|
||||
# 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 SRV-ID and XmppAddr identifiers
|
||||
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
|
||||
if [[ "$tag" == "A0" ]]; then
|
||||
# This is an otherName.
|
||||
if [[ $len_name -gt 18 ]] && ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] || \
|
||||
[[ "${dercert:i:20}" == "06082B06010505070807" ]] ); then
|
||||
# According to the OID, this is either an SRV-ID or XmppAddr.
|
||||
j=$i+20
|
||||
if [[ "${dercert:j:2}" == "A0" ]]; then
|
||||
j+=2
|
||||
if [[ "${dercert:j:1}" == "8" ]]; then
|
||||
j+=1
|
||||
j+=2*0x${dercert:j:1}+1
|
||||
else
|
||||
j+=2
|
||||
fi
|
||||
if ( [[ "${dercert:i:20}" == "06082B06010505070805" ]] && [[ "${dercert:j:2}" == "0C" ]] ) || \
|
||||
( [[ "${dercert:i:20}" == "06082B06010505070807" ]] && [[ "${dercert:j:2}" == "16" ]] ); then
|
||||
# XmppAddr should be encoded as UTF8STRING (0C) and
|
||||
# SRV-ID should be encoded IA5STRING (16).
|
||||
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
|
||||
if [[ $len1 -ne 0 ]]; then
|
||||
san="$(asciihex_to_binary_file "${dercert:j:len1}" "/dev/stdout")"
|
||||
if [[ "${dercert:i:20}" == "06082B06010505070805" ]]; then
|
||||
xmppaddr+="$san "
|
||||
else
|
||||
srv_id+="$san "
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
[[ -n "$srv_id" ]] && HAS_DNS_SANS=true
|
||||
[[ -n "$xmppaddr" ]] && HAS_DNS_SANS=true
|
||||
while read -d " " san; do
|
||||
[[ -n "$san" ]] && [[ $(toupper "$san") == "_XMPP-SERVER.$servername" ]] && subret=1 && break
|
||||
done <<< "$srv_id"
|
||||
if [[ $subret -eq 0 ]]; then
|
||||
while read -d " " san; do
|
||||
[[ -n "$san" ]] && [[ $(toupper "$san") == "$servername" ]] && subret=1 && break
|
||||
done <<< "$xmppaddr"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check whether any of the DNS names in the certificate are wildcard names
|
||||
# that match the servername
|
||||
@ -7572,7 +7691,7 @@ certificate_info() {
|
||||
fi
|
||||
|
||||
out "$indent"; pr_bold " Trust (hostname) "
|
||||
compare_server_name_to_cert "$NODE" "$HOSTCERT"
|
||||
compare_server_name_to_cert "$HOSTCERT"
|
||||
trust_sni=$?
|
||||
|
||||
# Find out if the subjectAltName extension is present and contains
|
||||
@ -7581,8 +7700,7 @@ certificate_info() {
|
||||
# identifier of CN-ID if the presented identifiers include a DNS-ID,
|
||||
# SRV-ID, URI-ID, or any application-specific identifier types
|
||||
# supported by the client.
|
||||
grep -A2 "Subject Alternative Name" <<< "$cert_txt" | grep -q "DNS:" && \
|
||||
has_dns_sans=true || has_dns_sans=false
|
||||
has_dns_sans=$HAS_DNS_SANS
|
||||
|
||||
case $trust_sni in
|
||||
0) trustfinding="certificate does not match supplied URI" ;;
|
||||
@ -7631,11 +7749,9 @@ certificate_info() {
|
||||
fi
|
||||
|
||||
if [[ -n "$cn_nosni" ]]; then
|
||||
compare_server_name_to_cert "$NODE" "$HOSTCERT.nosni"
|
||||
compare_server_name_to_cert "$HOSTCERT.nosni"
|
||||
trust_nosni=$?
|
||||
$OPENSSL x509 -in "$HOSTCERT.nosni" -noout -text 2>>$ERRFILE | \
|
||||
grep -A2 "Subject Alternative Name" | grep -q "DNS:" && \
|
||||
has_dns_sans_nosni=true || has_dns_sans_nosni=false
|
||||
has_dns_sans_nosni=$HAS_DNS_SANS
|
||||
fi
|
||||
|
||||
# See issue #733.
|
||||
@ -8035,7 +8151,7 @@ run_server_defaults() {
|
||||
# It should be displayed if it is either a match for the
|
||||
# $NODE being tested or if it has the same subject
|
||||
# (CN and SAN) as other certificates for this host.
|
||||
compare_server_name_to_cert "$NODE" "$HOSTCERT"
|
||||
compare_server_name_to_cert "$HOSTCERT"
|
||||
[[ $? -ne 0 ]] && success[n]=0 || success[n]=1
|
||||
|
||||
if [[ ${success[n]} -ne 0 ]]; then
|
||||
|
Loading…
Reference in New Issue
Block a user