From 946506f3ac55ff312bc0ca2ea48bd683aaa3f18c Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Sun, 3 Jul 2016 19:52:48 +0200 Subject: [PATCH 1/8] This Fixes #258 - Checks if /dev/fd is mounted on FreeBSD --- testssl.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testssl.sh b/testssl.sh index 73836a5..38eef5f 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6643,6 +6643,13 @@ openssl_age() { outln } +bsd-bash() { + if [[ $(uname) -eq "FreeBSD" ]] && [[ $(count_lines "$(ls /dev/fd)") -le 3 ]]; then + echo "You need to mount fdescfs on FreeBSD: mount -t fdescfs fdesc /dev/fd" + exit 1 + fi +} + help() { cat << EOF @@ -8091,6 +8098,7 @@ maketempf mybanner check_proxy openssl_age +bsd-bash # TODO: it is ugly to have those two vars here --> main() ret=0 From a64847098861f7667a59ffc88a9e4b0ea0c2eb80 Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Mon, 4 Jul 2016 17:21:24 +0200 Subject: [PATCH 2/8] Not done yet, but the basic code is working --- testssl.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/testssl.sh b/testssl.sh index 38eef5f..a290959 100755 --- a/testssl.sh +++ b/testssl.sh @@ -960,6 +960,7 @@ run_hpkp() { local i local hpkp_headers local first_hpkp_header + local ca_bundles="$INSTALL_DIR/etc/*.pem" if [[ ! -s $HEADERFILE ]]; then run_http_header "$1" || return 3 @@ -1029,21 +1030,99 @@ run_hpkp() { if [[ ! -s "$HOSTCERT" ]]; then get_host_cert || return 1 fi + + # Get the pins first + pins=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') + # get the key fingerprint from the host certificate hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \ $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)" # compare it with the ones provided in the header - while read hpkp_key; do + for hpkp_key in $(echo "$pins"); do if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then out "\n$spaces matching host key: " pr_done_good "$hpkp_key" - fileout "hpkp_keymatch" "OK" "Key matches a key pinned in the HPKP header" + fileout "hpkp_keymatch" "OK" "Host certificate key matches a key pinned in the HPKP header" key_found=true fi debugme out "\n $hpkp_key | $hpkp_key_hostcert" - done < <(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') + done + + if ! $key_found ; then + # Get keys from intermediate certificates + $OPENSSL s_client -showcerts $STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE + # Place the server's certificate in $HOSTCERT and any intermediate + # certificates that were provided in $TEMPDIR/intermediatecerts.pem + # http://backreference.org/2010/05/09/ocsp-verification-with-openssl/ + awk -v n=-1 "/Certificate chain/ {start=1} + /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } + inc { print > (\"$TEMPDIR/level\" n \".crt\") } + /---END CERTIFICATE-----/{ inc=0 }" $TMPFILE + nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)") + rm $TEMPDIR/level0.crt + + # Compare them against the hashes found + if [[ nrsaved -ge 2 ]]; then + echo -n "" > "/tmp/hashes.intermediate" + for cert_fname in $TEMPDIR/level?.crt; do + hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" + issuer="$(get_cn_from_cert $cert_fname)" + [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") + echo "$hpkp_key_ca $bundle_name $issuer" >> "/tmp/hashes.intermediate" + done + for hpkp_key in $(echo $pins); do + hpkp_matches=$(grep "$hpkp_key" /tmp/hashes.intermediate) + if [[ -n $hpkp_matches ]]; then + # We have a winner! + key_found=true + out "\n$spaces Intermediate CA match : " + pr_done_good "$hpkp_matches" + fileout "hpkp_keymatch" "OK" "Intermediate CA key matches a key pinned in the HPKP header\nKey/CA: $hpkp_matches" + fi + done + fi + fi + + if ! $key_found ; then + # Get keys from Root CAs + for bundle_fname in $ca_bundles; do + local bundle_name=$(basename ${bundle_fname//.pem}) + if [[ ! -r $bundle_fname ]]; then + pr_warningln "\"$bundle_fname\" cannot be found / not readable" + return 7 + fi + debugme printf -- " %-12s" "${certificate_file[i]}" + # Split up the certificate bundle + awk -v n=-1 "BEGIN {start=1} + /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } + inc { print >> (\"/tmp/$bundle_name.\" n \".crt\") ; close (\"/tmp/$bundle_name.\" n \".crt\") } + /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname + # Clear temp file + echo -n "" > "/tmp/hashes.$bundle_name" + for cert_fname in /tmp/$bundle_name.*.crt; do + hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" + issuer="$(get_cn_from_cert $cert_fname)" + [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") + echo "$hpkp_key_ca $bundle_name : $issuer" >> "/tmp/hashes.$bundle_name" + done + done + for hpkp_key in $(echo $pins); do + hpkp_matches=$(grep -h "$hpkp_key" /tmp/hashes.*) + if [[ -n $hpkp_matches ]]; then + # We have a winner! + key_found=true + out "\n$spaces Root CA match : " + pr_done_goodln "$hpkp_key" + out "$hpkp_matches" + + fileout "hpkp_keymatch" "OK" "Root CA key matches a key pinned in the HPKP header\nKey/OS/CA: $hpkp_matches" + fi + done + exit 99 + fi + + # If all else fails... if ! $key_found ; then - out "\n$spaces" pr_svrty_high " No matching key for pins found " out "(CAs pinned? -- not checked for yet)" fileout "hpkp_keymatch" "DEBUG" "The TLS key does not match any key pinned in the HPKP header. If you pinned a CA key you can ignore this" From 3049425740f746e77c95b550ec9ffa61f38d3b01 Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Mon, 4 Jul 2016 17:25:48 +0200 Subject: [PATCH 3/8] Checks for CA and intermediate pins too. Need to clean up output and write unit tests --- testssl.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/testssl.sh b/testssl.sh index a290959..eee5a8c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1063,15 +1063,15 @@ run_hpkp() { # Compare them against the hashes found if [[ nrsaved -ge 2 ]]; then - echo -n "" > "/tmp/hashes.intermediate" + echo -n "" > "$TEMPDIR/hashes.intermediate" for cert_fname in $TEMPDIR/level?.crt; do hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" issuer="$(get_cn_from_cert $cert_fname)" [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") - echo "$hpkp_key_ca $bundle_name $issuer" >> "/tmp/hashes.intermediate" + echo "$hpkp_key_ca $bundle_name $issuer" >> "$TEMPDIR/hashes.intermediate" done for hpkp_key in $(echo $pins); do - hpkp_matches=$(grep "$hpkp_key" /tmp/hashes.intermediate) + hpkp_matches=$(grep "$hpkp_key" $TEMPDIR/hashes.intermediate) if [[ -n $hpkp_matches ]]; then # We have a winner! key_found=true @@ -1095,30 +1095,29 @@ run_hpkp() { # Split up the certificate bundle awk -v n=-1 "BEGIN {start=1} /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print >> (\"/tmp/$bundle_name.\" n \".crt\") ; close (\"/tmp/$bundle_name.\" n \".crt\") } + inc { print >> (\"$TEMPDIR/$bundle_name.\" n \".crt\") ; close (\"$TEMPDIR/$bundle_name.\" n \".crt\") } /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname # Clear temp file - echo -n "" > "/tmp/hashes.$bundle_name" - for cert_fname in /tmp/$bundle_name.*.crt; do + echo -n "" > "$TEMPDIR/hashes.$bundle_name" + for cert_fname in $TEMPDIR/$bundle_name.*.crt; do hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" issuer="$(get_cn_from_cert $cert_fname)" [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") - echo "$hpkp_key_ca $bundle_name : $issuer" >> "/tmp/hashes.$bundle_name" + echo "$hpkp_key_ca $bundle_name : $issuer" >> "$TEMPDIR/hashes.$bundle_name" done done for hpkp_key in $(echo $pins); do - hpkp_matches=$(grep -h "$hpkp_key" /tmp/hashes.*) + hpkp_matches=$(grep -h "$hpkp_key" $TEMPDIR/hashes.*) if [[ -n $hpkp_matches ]]; then # We have a winner! key_found=true out "\n$spaces Root CA match : " pr_done_goodln "$hpkp_key" - out "$hpkp_matches" + #out "$hpkp_matches" fileout "hpkp_keymatch" "OK" "Root CA key matches a key pinned in the HPKP header\nKey/OS/CA: $hpkp_matches" fi done - exit 99 fi # If all else fails... From e280cac2af8007f9f0c6ac5011c0cedf110c839f Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Mon, 4 Jul 2016 17:30:32 +0200 Subject: [PATCH 4/8] Without bash-bsd check --- testssl.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/testssl.sh b/testssl.sh index 8d8b566..ec38d0a 100755 --- a/testssl.sh +++ b/testssl.sh @@ -6722,14 +6722,6 @@ check4openssl_oldfarts() { outln } -bsd-bash() { - if [[ $(uname) -eq "FreeBSD" ]] && [[ $(count_lines "$(ls /dev/fd)") -le 3 ]]; then - echo "You need to mount fdescfs on FreeBSD: mount -t fdescfs fdesc /dev/fd" - exit 1 - fi -} - - help() { cat << EOF @@ -8179,7 +8171,6 @@ maketempf mybanner check_proxy check4openssl_oldfarts -bsd-bash # TODO: it is ugly to have those two vars here --> main() ret=0 From 07f91a54bfcfa2c132e50f45611ea322fa5746fc Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Tue, 5 Jul 2016 18:10:36 +0200 Subject: [PATCH 5/8] Fixed the encoding for root/intermediate CAs now. --- testssl.sh | 173 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 97 insertions(+), 76 deletions(-) diff --git a/testssl.sh b/testssl.sh index ec38d0a..17785d6 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1029,104 +1029,124 @@ run_hpkp() { fileout "hpkp_preload" "INFO" "HPKP header is NOT marked for browser preloading" fi + # Get the pins first + pins=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') + + + # Look at the host certificate first + # get the key fingerprint from the host certificate if [[ ! -s "$HOSTCERT" ]]; then get_host_cert || return 1 fi - # Get the pins first - pins=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }') - - # get the key fingerprint from the host certificate hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \ $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)" - # compare it with the ones provided in the header - for hpkp_key in $(echo "$pins"); do + + + # Get keys/hashes from intermediate certificates + $OPENSSL s_client -showcerts $STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE + # Place the server's certificate in $HOSTCERT and any intermediate + # certificates that were provided in $TEMPDIR/intermediatecerts.pem + # http://backreference.org/2010/05/09/ocsp-verification-with-openssl/ + awk -v n=-1 "/Certificate chain/ {start=1} + /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } + inc { print > (\"$TEMPDIR/level\" n \".crt\") } + /---END CERTIFICATE-----/{ inc=0 }" $TMPFILE + nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)") + rm $TEMPDIR/level0.crt + + if [[ nrsaved -ge 2 ]]; then + echo -n "" > "$TEMPDIR/intermediate.hashes" + for cert_fname in $TEMPDIR/level?.crt; do + hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | $OPENSSL base64 -d | + $OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" + hpkp_name="$(get_cn_from_cert $cert_fname)" + [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout | sed 's/^subject= //') + echo "$hpkp_key_ca $hpkp_name" >> "$TEMPDIR/intermediate.hashes" + done + fi + rm $TEMPDIR/level*.crt + + # Get keys from Root CAs + for bundle_fname in $ca_bundles; do + local bundle_name=$(basename ${bundle_fname//.pem}) + if [[ ! -r $bundle_fname ]]; then + pr_warningln "\"$bundle_fname\" cannot be found / not readable" + return 7 + fi + debugme printf -- " %-12s" "${certificate_file[i]}" + # Split up the certificate bundle + awk -v n=-1 "BEGIN {start=1} + /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } + inc { print >> (\"$TEMPDIR/$bundle_name.\" n \".crt\") ; close (\"$TEMPDIR/$bundle_name.\" n \".crt\") } + /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname + # Clear temp file + echo -n "" > "$TEMPDIR/hashes.$bundle_name" + for cert_fname in $TEMPDIR/$bundle_name.*.crt; do + hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | $OPENSSL base64 -d | + $OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" + issuer=$(get_cn_from_cert $cert_fname) + [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") + echo "$hpkp_key_ca $bundle_name : $issuer" >> "$TEMPDIR/hashes.$bundle_name" + done + done + + pins_match=false + for hpkp_key in $(echo $pins); do + key_found=false + + # compare pin against the leaf certificate if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then - out "\n$spaces matching host key: " + out "\n$spaces Leaf certificate match : " pr_done_good "$hpkp_key" - fileout "hpkp_keymatch" "OK" "Host certificate key matches a key pinned in the HPKP header" + fileout "hpkp_$hpkp_key" "OK" "PIN $hpkp_key matches the leaf certificate" key_found=true + pins_match=true fi debugme out "\n $hpkp_key | $hpkp_key_hostcert" - done - if ! $key_found ; then - # Get keys from intermediate certificates - $OPENSSL s_client -showcerts $STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE - # Place the server's certificate in $HOSTCERT and any intermediate - # certificates that were provided in $TEMPDIR/intermediatecerts.pem - # http://backreference.org/2010/05/09/ocsp-verification-with-openssl/ - awk -v n=-1 "/Certificate chain/ {start=1} - /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print > (\"$TEMPDIR/level\" n \".crt\") } - /---END CERTIFICATE-----/{ inc=0 }" $TMPFILE - nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)") - rm $TEMPDIR/level0.crt - - # Compare them against the hashes found - if [[ nrsaved -ge 2 ]]; then - echo -n "" > "$TEMPDIR/hashes.intermediate" - for cert_fname in $TEMPDIR/level?.crt; do - hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" - issuer="$(get_cn_from_cert $cert_fname)" - [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") - echo "$hpkp_key_ca $bundle_name $issuer" >> "$TEMPDIR/hashes.intermediate" - done - for hpkp_key in $(echo $pins); do - hpkp_matches=$(grep "$hpkp_key" $TEMPDIR/hashes.intermediate) - if [[ -n $hpkp_matches ]]; then - # We have a winner! - key_found=true - out "\n$spaces Intermediate CA match : " - pr_done_good "$hpkp_matches" - fileout "hpkp_keymatch" "OK" "Intermediate CA key matches a key pinned in the HPKP header\nKey/CA: $hpkp_matches" - fi - done - fi - fi - - if ! $key_found ; then - # Get keys from Root CAs - for bundle_fname in $ca_bundles; do - local bundle_name=$(basename ${bundle_fname//.pem}) - if [[ ! -r $bundle_fname ]]; then - pr_warningln "\"$bundle_fname\" cannot be found / not readable" - return 7 + # Check for intermediate match + if ! $key_found ; then + hpkp_matches=$(grep "$hpkp_key" $TEMPDIR/intermediate.hashes) + if [[ -n $hpkp_matches ]]; then + # We have a winner! + key_found=true + pins_match=true + out "\n$spaces Intermediate CA match : " + pr_done_good "$hpkp_key" + out "\n$spaces $(echo $hpkp_matches|sed "s/^[a-zA-Z0-9\+\/]*=* *//")" + fileout "hpkp_$hpkp_key" "OK" "Intermediate CA key matches a key pinned in the HPKP header\nKey/CA: $hpkp_matches" fi - debugme printf -- " %-12s" "${certificate_file[i]}" - # Split up the certificate bundle - awk -v n=-1 "BEGIN {start=1} - /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } - inc { print >> (\"$TEMPDIR/$bundle_name.\" n \".crt\") ; close (\"$TEMPDIR/$bundle_name.\" n \".crt\") } - /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname - # Clear temp file - echo -n "" > "$TEMPDIR/hashes.$bundle_name" - for cert_fname in $TEMPDIR/$bundle_name.*.crt; do - hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC|$OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" - issuer="$(get_cn_from_cert $cert_fname)" - [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") - echo "$hpkp_key_ca $bundle_name : $issuer" >> "$TEMPDIR/hashes.$bundle_name" - done - done - for hpkp_key in $(echo $pins); do + fi + + if ! $key_found ; then hpkp_matches=$(grep -h "$hpkp_key" $TEMPDIR/hashes.*) if [[ -n $hpkp_matches ]]; then # We have a winner! key_found=true + pins_match=true out "\n$spaces Root CA match : " - pr_done_goodln "$hpkp_key" - #out "$hpkp_matches" - - fileout "hpkp_keymatch" "OK" "Root CA key matches a key pinned in the HPKP header\nKey/OS/CA: $hpkp_matches" + pr_done_good "$hpkp_key" + echo "$hpkp_matches"|sort -u|while read line; do + out "\n$spaces $(echo $line |sed "s/^[a-zA-Z0-9\+\/]*=* *//")" + done + fileout "hpkp_$hpkp_key" "OK" "Root CA key matches a key pinned in the HPKP header\nKey/OS/CA: $hpkp_matches" fi - done - fi + fi + + if ! $key_found ; then + # Houston we may have a problem + out "\n$spaces Unmatched key : " + pr_warning "$hpkp_key" + out " ( You can ignore this if this is a backup pin of your leaf certificate )" + fileout "hpkp_$hpkp_key" "WARN" "PIN $hpkp_key doesn't matchyour leaf certificate or and intermediate or known root CA\nThis could be ok if it is a backup pin for a leaf certificate" + fi + done # If all else fails... - if ! $key_found ; then + if ! $pins_match ; then pr_svrty_high " No matching key for pins found " - out "(CAs pinned? -- not checked for yet)" - fileout "hpkp_keymatch" "DEBUG" "The TLS key does not match any key pinned in the HPKP header. If you pinned a CA key you can ignore this" + fileout "hpkp_keymatch" "NOT ok" "None of the HPKP PINS match your leaf certificate, intermediate CA or known root CAs. You may have bricked this site" fi else out "--" @@ -3858,6 +3878,7 @@ get_cn_from_cert() { # for e.g. russian sites -esc_msb,utf8 works in an UTF8 terminal -- any way to check platform indepedent? # see x509(1ssl): subject="$($OPENSSL x509 -in $1 -noout -subject -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" + #echo "$subject" | sed "s/^.*CN\=//" | sed "s/\/.*$//" echo "$(awk -F'=' '/CN=/ { print $2 }' <<< "$subject")" return $? } From 95e42b2fdff1040fc6ddaf3dd8952b2a1293d94a Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Tue, 5 Jul 2016 23:33:20 +0200 Subject: [PATCH 6/8] Better displaying of findings --- testssl.sh | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/testssl.sh b/testssl.sh index e243063..726a7f0 100755 --- a/testssl.sh +++ b/testssl.sh @@ -482,7 +482,7 @@ fileout() { # ID, SEVERITY, FINDING if "$do_json"; then "$FIRST_FINDING" || echo -n "," >> $JSONFILE - echo -e " { + echo " { \"id\" : \"$1\", \"ip\" : \"$NODE/$NODEIP\", \"port\" : \"$PORT\", @@ -1041,7 +1041,7 @@ run_hpkp() { hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \ $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL base64)" - + hpkp_ca="$($OPENSSL x509 -in $HOSTCERT -issuer -noout|sed 's/^.*CN=//' | sed 's/\/.*$//')" # Get keys/hashes from intermediate certificates $OPENSSL s_client -showcerts $STARTTLS $BUGS $PROXY -showcerts -connect $NODEIP:$PORT ${sni[i]} $TMPFILE 2>$ERRFILE @@ -1061,6 +1061,7 @@ run_hpkp() { hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" hpkp_name="$(get_cn_from_cert $cert_fname)" + hpkp_ca="$($OPENSSL x509 -in $cert_fname -issuer -noout|sed 's/^.*CN=//' | sed 's/\/.*$//')" [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout | sed 's/^subject= //') echo "$hpkp_key_ca $hpkp_name" >> "$TEMPDIR/intermediate.hashes" done @@ -1068,26 +1069,25 @@ run_hpkp() { rm $TEMPDIR/level*.crt # Get keys from Root CAs + + # Clear temp file + echo -n "" > "$TEMPDIR/cahashes" for bundle_fname in $ca_bundles; do - local bundle_name=$(basename ${bundle_fname//.pem}) if [[ ! -r $bundle_fname ]]; then pr_warningln "\"$bundle_fname\" cannot be found / not readable" return 7 fi - debugme printf -- " %-12s" "${certificate_file[i]}" # Split up the certificate bundle awk -v n=-1 "BEGIN {start=1} /-----BEGIN CERTIFICATE-----/{ if (start) {inc=1; n++} } inc { print >> (\"$TEMPDIR/$bundle_name.\" n \".crt\") ; close (\"$TEMPDIR/$bundle_name.\" n \".crt\") } /---END CERTIFICATE-----/{ inc=0 }" $bundle_fname - # Clear temp file - echo -n "" > "$TEMPDIR/hashes.$bundle_name" for cert_fname in $TEMPDIR/$bundle_name.*.crt; do hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | $OPENSSL base64 -d | $OPENSSL dgst -sha256 -binary | $OPENSSL enc -base64)" issuer=$(get_cn_from_cert $cert_fname) [[ -n $hpkp_name ]] || hpkp_name=$($OPENSSL x509 -in "$cert_fname" -subject -noout| sed "s/^subject= //") - echo "$hpkp_key_ca $bundle_name : $issuer" >> "$TEMPDIR/hashes.$bundle_name" + echo "$hpkp_key_ca $issuer" >> "$TEMPDIR/cahashes" done done @@ -1097,7 +1097,7 @@ run_hpkp() { # compare pin against the leaf certificate if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then - out "\n$spaces Leaf certificate match : " + out "\n\n$spaces Leaf cert match : " pr_done_good "$hpkp_key" fileout "hpkp_$hpkp_key" "OK" "PIN $hpkp_key matches the leaf certificate" key_found=true @@ -1112,33 +1112,44 @@ run_hpkp() { # We have a winner! key_found=true pins_match=true - out "\n$spaces Intermediate CA match : " + out "\n\n$spaces Sub CA match : " pr_done_good "$hpkp_key" out "\n$spaces $(echo $hpkp_matches|sed "s/^[a-zA-Z0-9\+\/]*=* *//")" - fileout "hpkp_$hpkp_key" "OK" "Intermediate CA key matches a key pinned in the HPKP header\nKey/CA: $hpkp_matches" + fileout "hpkp_$hpkp_key" "OK" "Intermediate CA key matches a key pinned in the HPKP header.\\nKey/CA: $hpkp_matches" fi fi if ! $key_found ; then - hpkp_matches=$(grep -h "$hpkp_key" $TEMPDIR/hashes.*) + hpkp_matches=$(grep -h "$hpkp_key" $TEMPDIR/cahashes | sort -u) if [[ -n $hpkp_matches ]]; then # We have a winner! key_found=true pins_match=true - out "\n$spaces Root CA match : " + if [[ $(count_lines "$hpkp_matches") -eq 1 ]]; then + match_ca=$(echo "$hpkp_matches" | sed "s/[a-zA-Z0-9\+\/]*=* *//") + else + match_ca="" + fi + out "\n\n$spaces Root CA match : " pr_done_good "$hpkp_key" echo "$hpkp_matches"|sort -u|while read line; do out "\n$spaces $(echo $line |sed "s/^[a-zA-Z0-9\+\/]*=* *//")" done - fileout "hpkp_$hpkp_key" "OK" "Root CA key matches a key pinned in the HPKP header\nKey/OS/CA: $hpkp_matches" + if [[ $match_ca == $hpkp_ca ]]; then + pr_done_good "\n$spaces This CA is part of the chain" + fileout "hpkp_$hpkp_key" "OK" "Root CA key matches a key pinned in the HPKP header\\nKey/OS/CA: $hpkp_matches\\nThe CA is part of the chain" + else + out "\n$spaces This CA is not part of the chain and likely a backup PIN" + fileout "hpkp_$hpkp_key" "OK" "Root CA key matches a key pinned in the HPKP header\\nKey/OS/CA: $hpkp_matches\\nThe CA is not part of the chain, this is a backup PIN" + fi fi fi if ! $key_found ; then # Houston we may have a problem - out "\n$spaces Unmatched key : " + out "\n\n$spaces Unmatched key : " pr_warning "$hpkp_key" - out " ( You can ignore this if this is a backup pin of your leaf certificate )" + out "\n$spaces ( This is OK for a backup pin of a leaf cert )" fileout "hpkp_$hpkp_key" "WARN" "PIN $hpkp_key doesn't matchyour leaf certificate or and intermediate or known root CA\nThis could be ok if it is a backup pin for a leaf certificate" fi done From 845e6c13a321d49752a7112f5704fb91300da6f2 Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Tue, 5 Jul 2016 23:55:19 +0200 Subject: [PATCH 7/8] And we have unit tests too (and found some bugs in the process) --- t/02_hpkp_pinning.t | 79 +++++++++++++++++++++++++++++++++++++++++++++ testssl.sh | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100755 t/02_hpkp_pinning.t diff --git a/t/02_hpkp_pinning.t b/t/02_hpkp_pinning.t new file mode 100755 index 0000000..8de07b1 --- /dev/null +++ b/t/02_hpkp_pinning.t @@ -0,0 +1,79 @@ +#!/usr/bin/env perl + +use strict; +use Test::More; +use Data::Dumper; +use JSON; + +my $tests = 0; + +my ( + $out, + $json, + $found, +); +# OK +pass("Running testssl.sh against ssl.sectionzero.org"); $tests++; +$out = `./testssl.sh -H --jsonfile tmp.json --color 0 ssl.sectionzero.org`; +$json = json('tmp.json'); + +# It is better to have findings in a hash +# Look for a leaf cert match in the process. +my $found = 0; +my %findings; +foreach my $f ( @$json ) { + $findings{$f->{id}} = $f; + if ( $f->{finding} =~ /matches the leaf certificate/ ) { + $found++; + } +} +is($found,1,"We found 1 'matches the leaf certificate' finding"); $tests++; +like($out,'/Leaf cert match/',"There is a 'Leaf cert match' in the text output"); $tests++; + +# Sub CA match +ok( exists $findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"},"We have a finding for key YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"); $tests++; +like($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{finding},'/Intermediate CA key matches a key pinned in the HPKP header/',"We have our Sub CA finding"); $tests++; +is($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{severity}, "OK", "The finding is ok"); $tests++; +like($out,'/Sub CA match \: YLh1dUR9y6Kja30RrAn7JKnbQG\/uEtLMkBgFF2Fuihg/',"There is a 'Sub CA match' in the text output"); $tests++; + +# Root CA match Lets encrypt +ok( exists $findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"},"We have a finding for key Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"); $tests++; +like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/Root CA key matches a key pinned in the HPKP header/',"This is a Root CA finding"); $tests++; +like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/DST Root CA X3/',"Correct Root CA"); $tests++; +like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/The CA is part of the chain/',"CA is indeed part of chain"); $tests++; +is($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{severity}, "OK", "The finding is ok"); $tests++; +like($out,'/Root CA match \: Vjs8r4z\+80wjNcr1YKepWQboSIRi63WsWXhIMN\+eWys/',"There is a 'Root CA match' in the text output"); $tests++; + +# Root CA StartCom +ok( exists $findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"},"We have a finding for key 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"); $tests++; +like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/Root CA key matches a key pinned in the HPKP header/',"This is a Root CA finding"); $tests++; +like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/StartCom Certification Authority/',"Correct Root CA"); $tests++; +like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/The CA is not part of the chain/',"CA is indeed NOT part of chain"); $tests++; +is($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{severity}, "OK", "The finding is ok"); $tests++; +like($out,'/Root CA match \: 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU/',"There is a 'Root CA match' in the text output"); $tests++; + +# Bad PIN +ok( exists $findings{"hpkp_123bad123bad123bad123bad123bad123bd123bad12"},"We have a finding for key 123bad123bad123bad123bad123bad123bd123bad12"); $tests++; +like($findings{"hpkp_123bad123bad123bad123bad123bad123bd123bad12"}->{finding},'/doesn\'t match anything/',"It doesn't match indeed"); $tests++; +is($findings{"hpkp_123bad123bad123bad123bad123bad123bd123bad12"}->{severity}, "WARN", "The finding is ok"); $tests++; +like($out,'/Unmatched key : 123bad123bad123bad123bad123bad123bd123bad12/',"There is an 'unmatched key' in the text output"); $tests++; + +like($findings{hpkp_keys}->{finding},'/5 keys pinned/',"5 keys pinned in json"); $tests++; +like($out,'/\# of keys: 5/',"5 keys pinned in text output"); $tests++; + +like($findings{hpkp_age}->{finding},'/90 days/',"90 days in json"); $tests++; +like($out,'/90 days/',"90 days in text output"); $tests++; + +like($findings{hpkp_subdomains}->{finding},'/this domain only/',"this domain only in json"); $tests++; +like($out,'/just this domain/',"just this domain text output"); $tests++; + +like($findings{hpkp_preload}->{finding},'/NOT marked for/',"no preloading in json"); $tests++; + +done_testing($tests); + +sub json($) { + my $file = shift; + $file = `cat $file`; + unlink $file; + return from_json($file); +} \ No newline at end of file diff --git a/testssl.sh b/testssl.sh index 726a7f0..74abd5c 100755 --- a/testssl.sh +++ b/testssl.sh @@ -1150,7 +1150,7 @@ run_hpkp() { out "\n\n$spaces Unmatched key : " pr_warning "$hpkp_key" out "\n$spaces ( This is OK for a backup pin of a leaf cert )" - fileout "hpkp_$hpkp_key" "WARN" "PIN $hpkp_key doesn't matchyour leaf certificate or and intermediate or known root CA\nThis could be ok if it is a backup pin for a leaf certificate" + fileout "hpkp_$hpkp_key" "WARN" "PIN $hpkp_key doesn't match anything.\nThis could be ok if it is a backup pin for a leaf certificate" fi done From 02fb4ef9ca96489b43bfb45adfe5ba12f1a0e40f Mon Sep 17 00:00:00 2001 From: Frank Breedijk Date: Wed, 6 Jul 2016 00:12:48 +0200 Subject: [PATCH 8/8] Debug begone --- testssl.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/testssl.sh b/testssl.sh index 74abd5c..43b2f10 100755 --- a/testssl.sh +++ b/testssl.sh @@ -3896,7 +3896,6 @@ get_cn_from_cert() { # for e.g. russian sites -esc_msb,utf8 works in an UTF8 terminal -- any way to check platform indepedent? # see x509(1ssl): subject="$($OPENSSL x509 -in $1 -noout -subject -nameopt multiline,-align,sname,-esc_msb,utf8,-space_eq 2>>$ERRFILE)" - #echo "$subject" | sed "s/^.*CN\=//" | sed "s/\/.*$//" echo "$(awk -F'=' '/CN=/ { print $2 }' <<< "$subject")" return $? }