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 $? }