(saving work): major cleanups for output readability and code

This commit is contained in:
Dirk 2016-10-06 18:53:25 +02:00
parent 5115055895
commit 1c5eb17729
2 changed files with 98 additions and 79 deletions

View File

@ -28,38 +28,38 @@ foreach my $f ( @$json ) {
}
}
is($found,1,"We found 1 'matches the host certificate' finding"); $tests++;
like($out,'/Host cert match/',"There is a 'Leaf cert match' in the text output"); $tests++;
like($out,'/Host cert/',"There is a 'host cert match' in the text output"); $tests++;
# Sub CA match
ok( exists $findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"},"We have a finding for SPKI YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"); $tests++;
like($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{finding},'/Intermediate CA matches a key pinned in the HPKP header/',"We have our Sub CA finding"); $tests++;
like($findings{"hpkp_YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg"}->{finding},'/matches Intermediate CA \'Let\'s Encrypt Authority X3\' 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++;
like($out,'/Sub CA\: 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 SPKI Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"); $tests++;
like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/Root CA matches a key pinned in the HPKP header/',"This is a Root CA finding"); $tests++;
like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/matches Root CA \'DST Root CA X3\' 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++;
like($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{finding},'/matches Root CA \'DST Root CA X3\' pinned in the HPKP header\. \(Root CA part of the chain\)/',"CA is indeed part of chain"); $tests++;
is($findings{"hpkp_Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys"}->{severity}, "INFO", "The finding is informational"); $tests++;
like($out,'/Root CA match\: Vjs8r4z\+80wjNcr1YKepWQboSIRi63WsWXhIMN\+eWys/',"There is a 'Root CA match' in the text output"); $tests++;
like($out,'/Root CA\: 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 SPKI 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"); $tests++;
like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/Root CA matches a key pinned in the HPKP header/',"This is a Root CA finding"); $tests++;
like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/matches Root CA \'StartCom Certification Authority\' 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++;
like($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{finding},'/matches Root CA \'StartCom Certification Authority\' pinned in the HPKP header\. \(Root backup SPKI\)/',"CA is indeed NOT part of chain"); $tests++;
is($findings{"hpkp_5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU"}->{severity}, "INFO", "The finding is informational"); $tests++;
like($out,'/Root CA match\: 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU/',"There is a 'Root CA match' in the text output"); $tests++;
like($out,'/Backups\: 5C8kvU039KouVrl52D0eZSGf4Onjo4Khs8tmyTlV3nU/',"There is a 'Root CA match' in the text output"); $tests++;
# Bad PIN
ok( exists $findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"},"We have a finding for SPKI MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"); $tests++;
like($findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"}->{finding},'/doesn\'t match anything/',"It doesn't match indeed"); $tests++;
is($findings{"hpkp_MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI"}->{severity}, "INFO", "The finding is informational"); $tests++;
like($out,'/Unmatched SPKI\: MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI/',"There is an 'unmatched key' in the text output"); $tests++;
like($out,'/MTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTIzYmFkMTI/',"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_spkis}->{finding},'/5 keys pinned/',"5 keys pinned in json"); $tests++;
like($out,'/5 keys/',"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++;

View File

@ -369,6 +369,7 @@ pr_off() { [[ "$COLOR" -ne 0 ]] && out "\033[m"; }
pr_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[1m$1" || out "$1"; pr_off; }
pr_boldln() { pr_bold "$1" ; outln; }
pr_italic() { [[ "$COLOR" -ne 0 ]] && out "\033[3m$1" || out "$1"; pr_off; }
pr_italicln() { pr_italic "$1" ; outln; }
pr_underline() { [[ "$COLOR" -ne 0 ]] && out "\033[4m$1" || out "$1"; pr_off; }
pr_reverse() { [[ "$COLOR" -ne 0 ]] && out "\033[7m$1" || out "$1"; pr_off; }
pr_reverse_bold() { [[ "$COLOR" -ne 0 ]] && out "\033[7m\033[1m$1" || out "$1"; pr_off; }
@ -535,7 +536,7 @@ colon_to_spaces() {
}
strip_lf() {
echo "$1" | tr -d '\n' | tr -d '\r'
tr -d '\n' <<< "$1" | tr -d '\r'
}
strip_spaces() {
@ -962,13 +963,15 @@ run_hpkp() {
local -i hpkp_age_sec
local -i hpkp_age_days
local -i hpkp_nr_keys
local hpkp_key hpkp_key_hostcert
local -a backup_keys
local hpkp_spki hpkp_spki_hostcert
local -a backup_spki
local spaces=" "
local key_found=false
local spaces_indented=" "
local certificate_found=false
local i
local hpkp_headers
local first_hpkp_header
local spki
local ca_hashes="$INSTALL_DIR/etc/ca_hashes.txt"
if [[ ! -s $HEADERFILE ]]; then
@ -992,7 +995,7 @@ run_hpkp() {
out "\n$spaces Examining first one: "
first_hpkp_header=$(awk -F':' '/Public-Key-Pins/ { print $1 }' $HEADERFILE | head -1)
pr_italic "$first_hpkp_header, "
fileout "hpkp_multiple" "WARN" "Multiple HPKP headershpkp_headers. Using first header: $first_hpkp_header"
fileout "hpkp_multiple" "WARN" "Multiple HPKP headers $hpkp_headers. Using first header: $first_hpkp_header"
fi
# remove leading Public-Key-Pins*, any colons, double quotes and trailing spaces and taking the first -- whatever that is
@ -1003,13 +1006,13 @@ run_hpkp() {
tr ' ' '\n' < $TMPFILE.2 >$TMPFILE
hpkp_nr_keys=$(grep -ac pin-sha $TMPFILE)
out "# of keys: "
if [[ $hpkp_nr_keys -eq 1 ]]; then
pr_svrty_high "1 (NOT ok), "
fileout "hpkp_keys" "HIGH" "Only one key pinned in HPKP header, this means the site may become unavailable if the key is revoked"
pr_svrty_high "1 key (NOT ok), "
fileout "hpkp_spkis" "HIGH" "Only one key pinned in HPKP header, this means the site may become unavailable if the key is revoked"
else
out "$hpkp_nr_keys, "
fileout "hpkp_keys" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked"
pr_done_good "$hpkp_nr_keys"
out " keys, "
fileout "hpkp_spkis" "OK" "$hpkp_nr_keys keys pinned in HPKP header, additional keys are available if the current key is revoked"
fi
# print key=value pair with awk, then strip non-numbers, to be improved with proper parsing of key-value with awk
@ -1035,9 +1038,9 @@ run_hpkp() {
fileout "hpkp_preload" "INFO" "HPKP header is NOT marked for browser preloading"
fi
# Get the spki first
# Get the SPKIs first
spki=$(tr ';' '\n' < $TMPFILE | tr -d ' ' | tr -d '\"' | awk -F'=' '/pin.*=/ { print $2 }')
debugme outln "\n$spki"
# Look at the host certificate first
# get the key fingerprint from the host certificate
@ -1045,7 +1048,7 @@ run_hpkp() {
get_host_cert || return 1
fi
hpkp_key_hostcert="$($OPENSSL x509 -in $HOSTCERT -pubkey -noout | grep -v PUBLIC | \
hpkp_spki_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/\/.*$//')"
@ -1061,106 +1064,122 @@ run_hpkp() {
nrsaved=$(count_words "$(echo $TEMPDIR/level?.crt 2>/dev/null)")
rm $TEMPDIR/level0.crt 2>/dev/null
echo -n > "$TEMPDIR/intermediate.hashes"
printf ""> "$TEMPDIR/intermediate.hashes"
if [[ nrsaved -ge 2 ]]; then
for cert_fname in $TEMPDIR/level?.crt; do
hpkp_key_ca="$($OPENSSL x509 -in "$cert_fname" -pubkey -noout | grep -v PUBLIC | $OPENSSL base64 -d |
hpkp_spki_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"
echo "$hpkp_spki_ca $hpkp_name" >> "$TEMPDIR/intermediate.hashes"
done
fi
# This is where the matching magic happens...
# This is where the matching magic starts, first host certificate, intermediate, then root out of the stores
spki_match=false
has_backup_spki=false
i=0
for hpkp_key in $spki; do
key_found=false
# compare spki against the host certificate
if [[ "$hpkp_key_hostcert" == "$hpkp_key" ]] || [[ "$hpkp_key_hostcert" == "$hpkp_key=" ]]; then
# We have a match
key_found=true
for hpkp_spki in $spki; do
certificate_found=false
# compare collected SPKIs against the host certificate
if [[ "$hpkp_spki_hostcert" == "$hpkp_spki" ]] || [[ "$hpkp_spki_hostcert" == "$hpkp_spki=" ]]; then
certificate_found=true # We have a match
spki_match=true
out "\n$spaces Host cert match: "
pr_done_good "$hpkp_key"
fileout "hpkp_$hpkp_key" "OK" "SPKI $hpkp_key matches the host certificate"
out "\n$spaces_indented Host cert: "
pr_done_good "$hpkp_spki"
fileout "hpkp_$hpkp_spki" "OK" "SPKI $hpkp_spki matches the host certificate"
fi
debugme out "\n $hpkp_key | $hpkp_key_hostcert"
debugme out "\n $hpkp_spki | $hpkp_spki_hostcert"
# Check for intermediate match
if ! $key_found; then
hpkp_matches=$(grep "$hpkp_key" $TEMPDIR/intermediate.hashes 2>/dev/null)
if [[ -n $hpkp_matches ]]; then
if ! "$certificate_found"; then
hpkp_matches=$(grep "$hpkp_spki" $TEMPDIR/intermediate.hashes 2>/dev/null)
if [[ -n $hpkp_matches ]]; then # hpkp_matches + hpkp_spki + '='
# We have a match
key_found=true
certificate_found=true
spki_match=true
out "\n$spaces Sub CA match: "
pr_done_good "$hpkp_key"
out "\n$spaces \"$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )\""
fileout "hpkp_$hpkp_key" "OK" "Intermediate CA matches a key pinned in the HPKP header. Key/CA: $hpkp_matches"
out "\n$spaces_indented Sub CA: "
pr_done_good "$hpkp_spki"
ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )"
pr_italic " $ca_cn"
fileout "hpkp_$hpkp_spki" "OK" "SPKI $hpkp_spki matches Intermediate CA \"$ca_cn\" pinned in the HPKP header"
fi
fi
if ! $key_found; then
# we compare now against a precompiled list of SPKIs against the ROOT CAs we have!
hpkp_matches=$(grep -h "$hpkp_key" $ca_hashes | sort -u)
# we compare now against a precompiled list of SPKIs against the ROOT CAs we have in $ca_hashes
if ! "$certificate_found"; then
hpkp_matches=$(grep -h "$hpkp_spki" $ca_hashes | sort -u)
if [[ -n $hpkp_matches ]]; then
key_found=true
certificate_found=true # root CA found
spki_match=true
if [[ $(count_lines "$hpkp_matches") -eq 1 ]]; then
# replace by awk
match_ca=$(sed "s/[a-zA-Z0-9\+\/]*=* *//" <<< "$hpkp_matches")
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 \"$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< "$line")\""
done
if [[ $match_ca == $hpkp_ca ]]; then
out " (part of the chain)"
fileout "hpkp_$hpkp_key" "INFO" "Root CA matches a key pinned in the HPKP header. Key/OS/CA: $hpkp_matches. The CA is part of the chain"
else
has_backup_spki=true
out " -- not part of chain, probable backup key"
fileout "hpkp_$hpkp_key" "INFO" "Root CA matches a key pinned in the HPKP header. Key/OS/CA: $hpkp_matches. The CA is not part of the chain, this is a backup SPKI"
ca_cn="$(sed "s/^[a-zA-Z0-9\+\/]*=* *//" <<< $"$hpkp_matches" )"
if [[ "$match_ca" == "$hpkp_ca" ]]; then # part of the chain
out "\n$spaces_indented Root CA: "
pr_done_good "$hpkp_spki"
pr_italic " $ca_cn"
fileout "hpkp_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned in the HPKP header. (Root CA part of the chain)"
else # not part of chain
match_ca=""
has_backup_spki=true # Root CA outside the chain --> we save it for unmatched
fileout "hpkp_$hpkp_spki" "INFO" "SPKI $hpkp_spki matches Root CA \"$ca_cn\" pinned in the HPKP header. (Root backup SPKI)"
backup_spki[i]="$(strip_lf "$hpkp_spki")" # save it for later
backup_spki_str[i]="$ca_cn" # also the name=CN of the root CA
i=$((i + 1))
fi
fi
fi
if ! $key_found; then
# still no success --> it's probably a backup SPKI
if ! "$certificate_found"; then
# Most likely a backup SPKI, unfortunately we can't tell for what it is: host, intermediates
has_backup_spki=true
backup_keys[i]="$hpkp_key"
backup_spki[i]="$(strip_lf "$hpkp_spki")" # save it for later
backup_spki_str[i]="" # no root ca
i=$((i + 1))
fileout "hpkp_$hpkp_key" "INFO" "SPKI $hpkp_key doesn't match anything. This is ok for a backup for any certificate"
# CVS/JSON output here for the sake of simplicity, rest we do en bloc below
fileout "hpkp_$hpkp_spki" "INFO" "SPKI $hpkp_spki doesn't match anything. This is ok for a backup for any certificate"
# CSV/JSON output here for the sake of simplicity, rest we do en bloc below
fi
done
if [[ $i -eq 1 ]]; then
out "\n$spaces Unmatched SPKI: "
outln "${backup_keys[0]}"
# now print every backup spki out we saved before
out "\n$spaces_indented Backups: "
# for i=0 manually do the same as below as there's other indentation here
if [[ -n "${backup_spki_str[0]}" ]]; then
pr_done_good "${backup_spki[0]}"
#out " Root CA: "
pr_italicln " ${backup_spki_str[0]}"
else
out "\n\n$spaces Unmatched SPKIs: "
outln "${backup_keys[0]}"
for ((i=1; i <= ${#backup_keys[@]} ;i++ )); do
outln "$spaces ${backup_keys[i]}"
done
outln "${backup_spki[0]}"
fi
# OK for SPKI backup of host or different intermediate certificate is ok!
# now for i=1
for ((i=1; i < ${#backup_spki[@]} ;i++ )); do
if [[ -n "${backup_spki_str[i]}" ]]; then
# it's a Root CA outside the chain
pr_done_good "$spaces_indented ${backup_spki[i]}"
#out " Root CA: "
pr_italicln " ${backup_spki_str[i]}"
else
outln "$spaces_indented ${backup_spki[i]}"
fi
done
# If all else fails...
if ! $spki_match; then
if ! "$spki_match"; then
"$has_backup_spki" && out "$spaces" # we had a few lines with backup SPKIs already
pr_svrty_highln " No matching key for SPKI found "
fileout "hpkp_keymatch" "HIGH" "None of the HPKP SPKI match your host certificate, intermediate CA or known root CAs. You may have bricked this site"
fileout "hpkp_spkimatch" "HIGH" "None of the SPKI match your host certificate, intermediate CA or known root CAs. You may have bricked this site"
fi
if ! $has_backup_spki; then
if ! "$has_backup_spki"; then
pr_svrty_highln " No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site. "
fileout "hpkp_backup" "HIGH" "No backup keys found. Loss/compromise of the currently pinned key(s) will lead to bricked site."
fi