From ed8f26b2f68dfaa0aa315c2a7a9b89266cedbc6d Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 28 Jan 2019 18:31:57 +0100 Subject: [PATCH 1/3] Improve standard cipher lists section To finalize #1157 following was done * move 3DES one line above * put 128 bit CBC ciphers (ARIA, Camellie and AES) SEED and IDEA into 128Bit * the remaining 256 bit ciphers NOT supporting AEAD Mac into high Also 128 bit ciphers are getting a small complaint (LOW, yellow) if available instead of red (for SEED and IDEA before) To ease the (future) output rated_output() is included, but it's not being used yet. Also often I have docker hosts for testing. If I use them while the external network is down, I still experience DNS timeouts. I added for dig timeout values which proved to be reasonable in my tests with and without network. Also if an IPv4 or an IPv6 address was supplied testssl.sh doesn't do (futile) DNS lookups anymore. --- testssl.sh | 109 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 27 deletions(-) diff --git a/testssl.sh b/testssl.sh index 0e82f6c..a908ae4 100755 --- a/testssl.sh +++ b/testssl.sh @@ -2987,7 +2987,7 @@ sub_cipherlists() { if [[ -n "$6" ]] || listciphers "$1" "$2" $proto; then if [[ -z "$6" ]] || ( "$FAST" && listciphers "$1" "$2" -tls1 ); then for proto in -no_ssl2 -tls1_2 -tls1_1 -tls1 -ssl3; do - if [[ "$proto" == "-tls1_2" ]]; then + if [[ "$proto" == -tls1_2 ]]; then # If $OPENSSL doesn't support TLSv1.3 or if no TLSv1.3 # ciphers are being tested, then a TLSv1.2 ClientHello # was tested in the first iteration. @@ -3061,24 +3061,23 @@ sub_cipherlists() { fileout "${jsonID}_$5" "MEDIUM" "not offered" fi ;; - 1) if [[ $sclient_success -eq 0 ]]; then # High is good to offer pr_svrty_good "offered (OK)" fileout "${jsonID}_$5" "OK" "offered" else - # FIXME: the rating could be readjusted if we knew the result of STRONG before + # FIXME: we penalize the absence of high but don't know the result of strong encryption yet (next) pr_svrty_medium "not offered" fileout "${jsonID}_$5" "MEDIUM" "not offered" fi ;; 0) if [[ $sclient_success -eq 0 ]]; then # medium is not that bad - pr_svrty_medium "offered" - fileout "${jsonID}_$5" "MEDIUM" "offered" + pr_svrty_low "offered" + fileout "${jsonID}_$5" "LOW" "offered" else - out "not offered (OK)" - fileout "${jsonID}_$5" "OK" "not offered" + out "not offered" + fileout "${jsonID}_$5" "INFO" "not offered" fi ;; -1) if [[ $sclient_success -eq 0 ]]; then @@ -3122,6 +3121,48 @@ sub_cipherlists() { return $ret } +# Generic function for a rated output, no used yet. +# arg1: rating from 2 to -4 if available or not +# arg2: no/yes: decides whether positive or negative logic will be applied and "not" will be printed +# arg3: jsonID +# +rated_output() { + local jsonID=$3 + local logic="" + + if [[ $2 == no ]] || [[ $2 == negative ]]; then + logic="not " + fi + case $1 in + 2) pr_svrty_best "${logic}offered (OK)" + fileout "${jsonID}" "OK" "${logic}offered" + ;; + 1) pr_svrty_good "${logic}offered (OK)" + fileout "${jsonID}" "OK" "${logic}offered" + ;; + 0) out "${logic}offered" + fileout "${jsonID}" "INFO" "${logic}offered" + ;; + -1) pr_svrty_low "${logic}offered" + fileout "${jsonID}" "LOW" "${logic}offered" + ;; + -2) pr_svrty_medium "${logic}offered" + fileout "${jsonID}" "MEDIUM" "${logic}offered" + ;; + -3) pr_svrty_high "${logic}offered (NOT ok)" + fileout "${jsonID}" "HIGH" "${logic}offered" + ;; + -4) pr_svrty_critical "${logic}offered (NOT ok)" + fileout "${jsonID}" "CRITICAL" "${logic}offered" + ;; + *) pr_warning "FIXME: error around $LINENO, (please report this)" + fileout "${jsonID}" "WARN" "return condition $2 when $1 unclear" + return 1 + ;; + esac + return 0 +} + openssl2rfc() { local rfcname="" @@ -5381,22 +5422,22 @@ run_cipherlists() { # ~ egrep -w '64|56|RC2|RC4' etc/cipher-mapping.txt | grep -v export local low_ciphers="00,15, 00,12, 00,0f, 00,0c, 00,09, 00,1e, 00,22, fe,fe, ff,e1, c0,11, c0,07, 00,66, c0,0c, c0,02, 00,05, 00,04, 00,92, 00,8a, 00,20, 00,24, c0,33, 00,8e, 00,ff" local sslv2_low_ciphers="01,00,80, 03,00,80, 08,00,80, 06,00,40, 06,01,40, FF,80,00" - # ~ egrep -w 128 etc/cipher-mapping.txt | egrep -v "Au=None|AEAD|ARIA|Camellia|AES|RC2|RC4" - local medium_ciphers="00,9a, 00,99, 00,98, 00,97, 00,96, 00,07, 00,21, 00,25, 00,ff" - local sslv2_medium_ciphers="05,00,80" # ~ egrep -w '3DES' etc/cipher-mapping.txt local tdes_ciphers="c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,ff" local sslv2_tdes_ciphers="07,00,c0, 07,01,c0" - # ~ equivalent to 'egrep -w "GOST|128|256" etc/cipher-mapping.txt | grep -v '=None' | egrep -vw 'RC4|AEAD|IDEA|SEED|RC2'. Attention: 127 ciphers currently - local high_ciphers="c0,28, c0,24, c0,14, c0,0a, c0,22, c0,21, c0,20, 00,b7, 00,b3, 00,91, c0,9b, c0,99, c0,97, 00,af, c0,95, 00,6b, 00,6a, 00,69, 00,68, 00,39, 00,38, 00,37, 00,36, c0,77, c0,73, 00,c4, 00,c3, 00,c2, 00,c1, 00,88, 00,87, 00,86, 00,85, c0,2a, c0,26, c0,0f, c0,05, c0,79, c0,75, 00,3d, 00,35, 00,c0, c0,38, c0,36, 00,84, 00,95, 00,8d, c0,3d, c0,3f, c0,41, c0,43, c0,45, c0,49, c0,4b, c0,4d, c0,4f, c0,65, c0,67, c0,69, c0,71, 00,80, 00,81, ff,00, ff,01, ff,02, ff,03, ff,85, c0,27, c0,23, c0,13, c0,09, c0,1f, c0,1e, c0,1d, 00,67, 00,40, 00,3f, 00,3e, 00,33, 00,32, 00,31, 00,30, c0,76, c0,72, 00,be, 00,bd, 00,bc, 00,bb, 00,45, 00,44, 00,43, 00,42, c0,29, c0,25, c0,0e, c0,04, c0,78, c0,74, 00,3c, 00,2f, 00,ba, c0,37, c0,35, 00,b6, 00,b2, 00,90, 00,41, c0,9a, c0,98, c0,96, 00,ae, c0,94, 00,94, 00,8c, c0,3c, c0,3e, c0,40, c0,42, c0,44, c0,48, c0,4a, c0,4c, c0,4e, c0,64, c0,66, c0,68, c0,70" - # no SSLv2 here and in strong - # ~ equivalent to 'grep AEAD etc/cipher-mapping.txt | grep -v Au=None' + # ~ egrep -w 128 etc/cipher-mapping.txt | egrep -v "Au=None|AEAD|RC2|RC4" + local medium_ciphers="00,07, 00,21, 00,25, 00,2F, 00,30, 00,31, 00,32, 00,33, 00,3C, 00,3E, 00,3F, 00,40, 00,41, 00,42, 00,43, 00,44, 00,45, 00,67, 00,8C, 00,90, 00,94, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,AE, 00,B2, 00,B6, 00,BA, 00,BB, 00,BC, 00,BD, 00,BE, C0,04, C0,09, C0,0E, C0,13, C0,1D, C0,1E, C0,1F, C0,23, C0,25, C0,27, C0,29, C0,35, C0,37, C0,3C, C0,3E, C0,40, C0,42, C0,44, C0,48, C0,4A, C0,4C, C0,4E, C0,64, C0,66, C0,68, C0,70, C0,72, C0,74, C0,76, C0,78, C0,94, C0,96, C0,98, C0,9A, 00, FF" + # Attention we have a SSLv2 cipher here: IDEA-CBC-MD5 / SSL_CK_IDEA_128_CBC_WITH_MD5 + local sslv2_medium_ciphers="05,00,80" + # ~ egrep -w "256" etc/cipher-mapping.txt | grep -v '=None' | egrep -vw 'RC4|AEAD|IDEA|SEED|RC2|GOST' + local high_ciphers="00,35, 00,36, 00,37, 00,38, 00,39, 00,3D, 00,68, 00,69, 00,6A, 00,6B, 00,84, 00,85, 00,86, 00,87, 00,88, 00,8D, 00,91, 00,95, 00,AF, 00,B3, 00,B7, 00,C0, 00,C1, 00,C2, 00,C3, 00,C4, C0,05, C0,0A, C0,0F, C0,14, C0,20, C0,21, C0,22, C0,24, C0,26, C0,28, C0,2A, C0,36, C0,38, C0,3D, C0,3F, C0,41, C0,43, C0,45, C0,49, C0,4B, C0,4D, C0,4F, C0,65, C0,67, C0,69, C0,71, C0,73, C0,75, C0,77, C0,79, C0,95, C0,97, C0,99, C0,9B" + # ~ grep AEAD etc/cipher-mapping.txt | grep -v Au=None local strong_ciphers="13,01, 13,02, 13,03, 13,04, 13,05, cc,14, cc,13, cc,15, c0,30, c0,2c, 00,a5, 00,a3, 00,a1, 00,9f, cc,a9, cc,a8, cc,aa, c0,af, c0,ad, c0,a3, c0,9f, 00,ad, 00,ab, cc,ae, cc,ad, cc,ac, c0,ab, c0,a7, c0,32, c0,2e, 00,9d, c0,a1, c0,9d, 00,a9, cc,ab, c0,a9, c0,a5, c0,51, c0,53, c0,55, c0,57, c0,59, c0,5d, c0,5f, c0,61, c0,63, c0,6b, c0,6d, c0,6f, c0,7b, c0,7d, c0,7f, c0,81, c0,83, c0,87, c0,89, c0,8b, c0,8d, c0,8f, c0,91, c0,93, 16,b7, 16,b8, 16,b9, 16,ba, c0,2f, c0,2b, 00,a4, 00,a2, 00,a0, 00,9e, c0,ae, c0,ac, c0,a2, c0,9e, 00,ac, 00,aa, c0,aa, c0,a6, c0,a0, c0,9c, 00,a8, c0,a8, c0,a4, c0,31, c0,2d, 00,9c, c0,50, c0,52, c0,54, c0,56, c0,58, c0,5c, c0,5e, c0,60, c0,62, c0,6a, c0,6c, c0,6e, c0,7a, c0,7c, c0,7e, c0,80, c0,82, c0,86, c0,88, c0,8a, c0,8c, c0,8e, c0,90, c0,92, 00,ff" local cwe="CWE-327" local cwe2="CWE-310" local cve="" - # decoding the SSLv3-TLS1.2 ciphers, e.g: + # decoding the >= SSLv3 ciphers in the code above , e.g: # echo "00,15, c0,11, fe,fe' | sed -e 's/00,/0x00,0x/g' -e 's/c0,/0xc0,0x/g' -e 's/cc,/0xcc,0x/g' -e 's/13,/0x13,0x/g' -e 's/16,/0x16,0x/g' -e 's/fe,/0xfe,0x/g' -e 's/ff,/0xff,0x/g' -e 's/, /\n/g' | \ # while read ci; do grep -wi $ci etc/cipher-mapping.txt; done @@ -5414,7 +5455,7 @@ run_cipherlists() { outln pr_headlineln " Testing cipher categories " outln - # argv[1]: cipher list to test in OpenSSL syntax (see ciphers(1ssl) or run 'openssl ciphers -v/-V)' + # argv[1]: cipher list to test in OpenSSL syntax (see ciphers(1ssl) or run 'openssl ciphers -v/-V)', TLS 1.3 ciphers will be treated automatically # argv[2]: string on console / HTML or "finding" # argv[3]: rating whether ok to offer # argv[4]: string to be appended for fileout @@ -5428,15 +5469,15 @@ run_cipherlists() { ret=$((ret + $?)) sub_cipherlists 'LOW:DES:RC2:RC4:!ADH:!EXP:!NULL' "" " LOW: 64 Bit + DES, RC[2,4] (w/o export) " -2 "LOW" "$low_ciphers" "$sslv2_low_ciphers" "$cve" "$cwe" ret=$((ret + $?)) - sub_cipherlists 'MEDIUM:!aNULL:!AES:!CAMELLIA:!ARIA:!CHACHA20:!3DES:!RC2:!RC4' \ - "" " Weak 128 Bit ciphers (SEED, IDEA) " -1 "128Bit" "$medium_ciphers" "$sslv2_medium_ciphers" "$cve" "$cwe2" + sub_cipherlists '3DES:!aNULL:!ADH' "" " Triple DES Ciphers " -1 "3DES" "$tdes_ciphers" "$sslv2_tdes_ciphers" "$cve" "$cwe2" ret=$((ret + $?)) - sub_cipherlists '3DES:!aNULL:!ADH' "" " Triple DES Ciphers (Medium) " 0 "3DES" "$tdes_ciphers" "$sslv2_tdes_ciphers" "$cve" "$cwe2" + sub_cipherlists 'MEDIUM:!aNULL:AES128:CAMELLIA128:ARIA128:!CHACHA20:!3DES:!RC2:!RC4:!AESCCM8:!AESCCM:!AESGCM:!ARIAGCM' \ + "" " 128 Bit ciphers (SEED, IDEA, 128 Bit CBC) " 0 "128Bit" "$medium_ciphers" "$sslv2_medium_ciphers" "$cve" "$cwe2" ret=$((ret + $?)) - sub_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES:!AESGCM:!CHACHA20:!AESGCM:!CamelliaGCM:!AESCCM8:!AESCCM' \ - "" " High encryption (AES+Camellia, no AEAD) " 1 "HIGH" "$high_ciphers" "" + sub_cipherlists 'HIGH:!NULL:!aNULL:!DES:!3DES:!AESGCM:!CHACHA20:!CamelliaGCM:!AESCCM:!AESCCM8:!AES128:!CAMELLIA128:!ARIAGCM:!ARIACCM' \ + "" " High encryption (AES/Aria/Camellia, !AEAD)" 1 "HIGH" "$high_ciphers" "" ret=$((ret + $?)) - sub_cipherlists 'AESGCM:CHACHA20:AESGCM:CamelliaGCM:AESCCM8:AESCCM' 'ALL' \ + sub_cipherlists 'AESGCM:CHACHA20:AESGCM:CamelliaGCM:AESCCM:ARIAGCM' 'ALL' \ " Strong encryption (AEAD ciphers) " 2 "STRONG" "$strong_ciphers" "" ret=$((ret + $?)) outln @@ -16482,6 +16523,11 @@ get_a_record() { echo 127.0.0.1 return 0 fi + if is_ipv4addr "$1"; then + # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS + echo $1 + return 0 + fi OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 check_resolver_bins if [[ "$NODE" == *.local ]]; then @@ -16495,7 +16541,7 @@ get_a_record() { fi if [[ -z "$ip4" ]]; then if type -p dig &> /dev/null ; then - ip4=$(filter_ip4_address $(dig +short -t a "$1" 2>/dev/null | awk '/^[0-9]/')) + ip4=$(filter_ip4_address $(dig +timeout=2 +tries=2 +short -t a "$1" 2>/dev/null | awk '/^[0-9]/')) fi fi if [[ -z "$ip4" ]]; then @@ -16523,6 +16569,14 @@ get_aaaa_record() { [[ "$NODNS" == none ]] && return 0 # if no DNS lookup was instructed, leave here OPENSSL_CONF="" # see https://github.com/drwetter/testssl.sh/issues/134 + if is_ipv6addr "$1"; then + # This saves walking through this. Also it avoids hangs e.g. if you run docker locally without reachabale DNS + echo "$1" + return 0 + elif is_ipv4addr "$1"; then + # we need also this here as get_aaaa_record is always called after get_a_record and we want to handle this at a low level + return 0 + fi check_resolver_bins if [[ -z "$ip6" ]]; then if [[ "$NODE" == *.local ]]; then @@ -16533,10 +16587,10 @@ get_aaaa_record() { else fatal "Local hostname given but no 'avahi-resolve' or 'dig' available." $ERR_DNSBIN fi + elif type -p dig &> /dev/null; then + ip6=$(filter_ip6_address $(dig +short +timeout=2 +tries=2 -t aaaa "$1" 2>/dev/null | awk '/^[0-9]/')) elif type -p host &> /dev/null ; then ip6=$(filter_ip6_address $(host -t aaaa "$1" | awk '/address/ { print $NF }')) - elif type -p dig &> /dev/null; then - ip6=$(filter_ip6_address $(dig +short -t aaaa "$1" 2>/dev/null | awk '/^[0-9]/')) elif type -p drill &> /dev/null; then ip6=$(filter_ip6_address $(drill aaaa "$1" | awk '/ANSWER SECTION/,/AUTHORITY SECTION/ { print $NF }' | awk '/^[0-9]/')) elif type -p nslookup &>/dev/null; then @@ -16567,7 +16621,7 @@ get_caa_rr_record() { OPENSSL_CONF="" check_resolver_bins if type -p dig &> /dev/null; then - raw_caa="$(dig $1 type257 +short)" + raw_caa="$(dig +timeout=3 +tries=3 $1 type257 +short)" # empty if no CAA record elif type -p drill &> /dev/null; then raw_caa="$(drill $1 type257 | awk '/'"^${1}"'.*CAA/ { print $5,$6,$7 }')" @@ -16739,7 +16793,8 @@ determine_rdns() { rDNS=$(dig -x $nodeip @224.0.0.251 -p 5353 +notcp +noall +answer | awk '/PTR/ { print $NF }') fi elif type -p dig &> /dev/null; then - rDNS=$(dig -x $nodeip +noall +answer | awk '/PTR/ { print $NF }') # +short returns also CNAME, e.g. openssl.org + # 1+2 should suffice. It's a compromise for if e.g. network is down but we have a docker/localhost server + rDNS=$(dig -x $nodeip +timeout=1 +tries=2 +noall +answer | awk '/PTR/ { print $NF }') # +short returns also CNAME, e.g. openssl.org elif type -p host &> /dev/null; then rDNS=$(host -t PTR $nodeip 2>/dev/null | awk '/pointer/ { print $NF }') elif type -p drill &> /dev/null; then From 7f8a0f2c8bdea8feba2ff57494fbb49784a80682 Mon Sep 17 00:00:00 2001 From: Dirk Date: Mon, 28 Jan 2019 21:23:51 +0100 Subject: [PATCH 2/3] Readd final 00,ff to high_ciphers to previous commit --- testssl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testssl.sh b/testssl.sh index a908ae4..cb36c62 100755 --- a/testssl.sh +++ b/testssl.sh @@ -5426,11 +5426,11 @@ run_cipherlists() { local tdes_ciphers="c0,12, c0,08, c0,1c, c0,1b, c0,1a, 00,16, 00,13, 00,10, 00,0d, c0,0d, c0,03, 00,0a, 00,93, 00,8b, 00,1f, 00,23, c0,34, 00,8f, fe,ff, ff,e0, 00,ff" local sslv2_tdes_ciphers="07,00,c0, 07,01,c0" # ~ egrep -w 128 etc/cipher-mapping.txt | egrep -v "Au=None|AEAD|RC2|RC4" - local medium_ciphers="00,07, 00,21, 00,25, 00,2F, 00,30, 00,31, 00,32, 00,33, 00,3C, 00,3E, 00,3F, 00,40, 00,41, 00,42, 00,43, 00,44, 00,45, 00,67, 00,8C, 00,90, 00,94, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,AE, 00,B2, 00,B6, 00,BA, 00,BB, 00,BC, 00,BD, 00,BE, C0,04, C0,09, C0,0E, C0,13, C0,1D, C0,1E, C0,1F, C0,23, C0,25, C0,27, C0,29, C0,35, C0,37, C0,3C, C0,3E, C0,40, C0,42, C0,44, C0,48, C0,4A, C0,4C, C0,4E, C0,64, C0,66, C0,68, C0,70, C0,72, C0,74, C0,76, C0,78, C0,94, C0,96, C0,98, C0,9A, 00, FF" + local medium_ciphers="00,07, 00,21, 00,25, 00,2F, 00,30, 00,31, 00,32, 00,33, 00,3C, 00,3E, 00,3F, 00,40, 00,41, 00,42, 00,43, 00,44, 00,45, 00,67, 00,8C, 00,90, 00,94, 00,96, 00,97, 00,98, 00,99, 00,9A, 00,AE, 00,B2, 00,B6, 00,BA, 00,BB, 00,BC, 00,BD, 00,BE, C0,04, C0,09, C0,0E, C0,13, C0,1D, C0,1E, C0,1F, C0,23, C0,25, C0,27, C0,29, C0,35, C0,37, C0,3C, C0,3E, C0,40, C0,42, C0,44, C0,48, C0,4A, C0,4C, C0,4E, C0,64, C0,66, C0,68, C0,70, C0,72, C0,74, C0,76, C0,78, C0,94, C0,96, C0,98, C0,9A, 00,ff" # Attention we have a SSLv2 cipher here: IDEA-CBC-MD5 / SSL_CK_IDEA_128_CBC_WITH_MD5 local sslv2_medium_ciphers="05,00,80" # ~ egrep -w "256" etc/cipher-mapping.txt | grep -v '=None' | egrep -vw 'RC4|AEAD|IDEA|SEED|RC2|GOST' - local high_ciphers="00,35, 00,36, 00,37, 00,38, 00,39, 00,3D, 00,68, 00,69, 00,6A, 00,6B, 00,84, 00,85, 00,86, 00,87, 00,88, 00,8D, 00,91, 00,95, 00,AF, 00,B3, 00,B7, 00,C0, 00,C1, 00,C2, 00,C3, 00,C4, C0,05, C0,0A, C0,0F, C0,14, C0,20, C0,21, C0,22, C0,24, C0,26, C0,28, C0,2A, C0,36, C0,38, C0,3D, C0,3F, C0,41, C0,43, C0,45, C0,49, C0,4B, C0,4D, C0,4F, C0,65, C0,67, C0,69, C0,71, C0,73, C0,75, C0,77, C0,79, C0,95, C0,97, C0,99, C0,9B" + local high_ciphers="00,35, 00,36, 00,37, 00,38, 00,39, 00,3D, 00,68, 00,69, 00,6A, 00,6B, 00,84, 00,85, 00,86, 00,87, 00,88, 00,8D, 00,91, 00,95, 00,AF, 00,B3, 00,B7, 00,C0, 00,C1, 00,C2, 00,C3, 00,C4, C0,05, C0,0A, C0,0F, C0,14, C0,20, C0,21, C0,22, C0,24, C0,26, C0,28, C0,2A, C0,36, C0,38, C0,3D, C0,3F, C0,41, C0,43, C0,45, C0,49, C0,4B, C0,4D, C0,4F, C0,65, C0,67, C0,69, C0,71, C0,73, C0,75, C0,77, C0,79, C0,95, C0,97, C0,99, C0,9B, 00,ff" # ~ grep AEAD etc/cipher-mapping.txt | grep -v Au=None local strong_ciphers="13,01, 13,02, 13,03, 13,04, 13,05, cc,14, cc,13, cc,15, c0,30, c0,2c, 00,a5, 00,a3, 00,a1, 00,9f, cc,a9, cc,a8, cc,aa, c0,af, c0,ad, c0,a3, c0,9f, 00,ad, 00,ab, cc,ae, cc,ad, cc,ac, c0,ab, c0,a7, c0,32, c0,2e, 00,9d, c0,a1, c0,9d, 00,a9, cc,ab, c0,a9, c0,a5, c0,51, c0,53, c0,55, c0,57, c0,59, c0,5d, c0,5f, c0,61, c0,63, c0,6b, c0,6d, c0,6f, c0,7b, c0,7d, c0,7f, c0,81, c0,83, c0,87, c0,89, c0,8b, c0,8d, c0,8f, c0,91, c0,93, 16,b7, 16,b8, 16,b9, 16,ba, c0,2f, c0,2b, 00,a4, 00,a2, 00,a0, 00,9e, c0,ae, c0,ac, c0,a2, c0,9e, 00,ac, 00,aa, c0,aa, c0,a6, c0,a0, c0,9c, 00,a8, c0,a8, c0,a4, c0,31, c0,2d, 00,9c, c0,50, c0,52, c0,54, c0,56, c0,58, c0,5c, c0,5e, c0,60, c0,62, c0,6a, c0,6c, c0,6e, c0,7a, c0,7c, c0,7e, c0,80, c0,82, c0,86, c0,88, c0,8a, c0,8c, c0,8e, c0,90, c0,92, 00,ff" local cwe="CWE-327" From 30da1cdd72ce7bb44dea1afd37dc42c234473aa9 Mon Sep 17 00:00:00 2001 From: David Cooper Date: Thu, 31 Jan 2019 11:51:16 -0500 Subject: [PATCH 3/3] 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. --- testssl.sh | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/testssl.sh b/testssl.sh index cb36c62..78fdcfa 100755 --- a/testssl.sh +++ b/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:". + # As certificates will rarely include a name encoded as an otherName, check the + # text version of the certificate for "othername:" before calling + # external functions to obtain the DER encoded certficate. + if [[ "$cert_txt" =~ X509v3\ Subject\ Alternative\ Name:.*othername:\ ]]; 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: