#!/bin/sh
LANG=C
\unalias -a
progname=${0##*/}
info() {
echo "$@" >&2
}
warning() {
echo "WARNING: $@" >&2
}
error() {
echo "ERROR: $@" >&2
exit 1
}
usage() {
echo "usage: ${progname} [user] [keyid ...]\n" >&2
exit 1
}
gpg=$(which gpg)
if [ $? -gt 0 -o -z "${gpg}" -o ! -x "${gpg}" ] ; then
error "Cannot find gpg"
fi
_gpg() {
${gpg} \
--display-charset utf-8 \
--no-greeting \
--no-secmem-warning \
--keyid-format long \
--list-options no-show-uid-validity \
"$@"
}
getkeybyid() {
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
'$5 ~ /^\([0-9A-F]{8}\)?'"$1"'$/i && $12 ~ /ESC/ { print $5 }'
}
getkeybyemail() {
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
'$10 ~ /<'"$1"'>/i && $12 ~ /ESC/ { print $5 }'
}
if [ $# -gt 0 ] ; then
id="$1"
shift
else
id=$(id -nu)
warning "No argument specified, calculating user ID"
fi
if echo "${id}" | egrep -q '^[0-9A-F]{16}$'; then
id_type="keyid"
set -- "${id}" $@
elif echo "${id}" | egrep -q '^[0-9A-F]{8}$'; then
id_type="keyid"
set -- "${id}" $@
elif echo "${id}" | egrep -iq '^[0-9a-z][-0-9a-z_]*@([-0-9a-z]+\.)[-0-9a-z]+$'; then
id_type="email"
email="${id}"
elif echo "${id}" | egrep -iq '^[0-9a-z][-0-9a-z_]*$'; then
id_type="login"
login="${id}"
email="${id}@FreeBSD.org"
else
error "Cannot recognize type of ${id} (keyid, login, or email)"
fi
if [ $# -ne 0 ] ; then
for arg ; do
case $(expr "${arg}" : '^[0-9A-Fa-f]\{8,16\}$') in
8)
warning "${arg}: recommend using 16-digit keyid"
;;
16)
;;
*)
warning "${arg} does not appear to be a valid key ID"
continue
;;
esac
keyid=$(getkeybyid "${arg}")
if [ -n "${keyid}" ] ; then
keyids="${keyids} ${keyid}"
else
warning "${arg} not found"
fi
done
else
keyids=$(getkeybyemail "${email}")
case $(echo "${keyids}" | wc -w) in
0)
error "no keys found for ${email}"
;;
1)
;;
*)
warning "Multiple keys found for <${email}>; checking all."
warning "If this is not what you want, specify a key ID" \
"on the command line."
;;
esac
fi
if [ -z "${keyids}" ] ; then
error "no valid keys were found"
fi
badkey() {
key_problems=" ${key_problems}$@
"
}
exitstatus=0
for key in ${keyids} ; do
key_problems=""
IFS_save="${IFS}"
key_info=$( ${gpg} --no-secmem-warning --export-options export-minimal --export ${key} \
| ${gpg} --no-secmem-warning --list-packets )
IFS=""
version=$( echo $key_info | \
awk '$1 == "version" && $3 == "algo" {sub(",", "", $2); print $2; exit 0}' )
IFS="${IFS_save}"
if [ $version -lt 4 ]; then
badkey "This key is a deprecated version $version key!"
fi
IFS=""
algonum=$( echo $key_info | \
awk '$1 == "version" && $3 == "algo" {sub(",", "", $4); print $4; exit 0}' )
IFS="${IFS_save}"
case ${algonum} in
"1") algo="RSA" ;;
"17") algo="DSA" ;;
"18") algo="ECC" ;;
"19") algo="ECDSA" ;;
"22") algo="EDDSA" ;;
*) algo="*UNKNOWN*" ;;
esac
IFS=""
bitlen=$( echo $key_info | \
awk -F : '$1 ~ "pkey" { gsub("[^0-9]*","", $2); print $2; exit 0}' )
IFS="${IFS_save}"
echo "key ${key}: ${algo}, ${bitlen} bits"
case ${algo} in
RSA) ;;
DSA) if [ "${bitlen}" -le 1024 ]; then \
badkey "DSA, but not DSA-2"; \
fi ;;
EDDSA) ;;
*) badkey "non-preferred algorithm"
esac
IFS=""
sig_algonum=$( echo $key_info | \
awk '$1 == "digest" && $2 == "algo" {sub(",", "", $3); print $3; exit 0}' )
IFS="${IFS_save}"
case sig_algonum in
1) sigs="MD5";;
2) sigs="SHA1";;
3) sigs="RIPEMD160";;
8) sigs="SHA256";;
9) sigs="SHA384";;
10) sigs="SHA512";;
11) sigs="SHA224";;
*)
esac
for sig in ${sigs}; do
if [ "${sig}" = "MD5" -o "${sig}" = "SHA1" ]; then
badkey "self-signature ${sig}"
fi
done
IFS=""
algopref=$( echo $key_info | \
awk -F : '$1 ~ "pref-hash-algos" {gsub("[^ 0-9]", "", $2); print $2; exit 0}' )
IFS="${IFS_save}"
set -- ${algopref}
if [ $1 -lt 4 ]; then
badkey "algorithm prefs do not have SHA-2 higher than MD5 or SHA1"
fi
expires=$( _gpg --list-keys ${key} | \
awk "/$keyid .*expires:/ {sub(\"[^-0-9]\", \"\", \$NF); print \$NF; exit 0}" )
if [ -z "${expires}" ]; then
badkey "this key does not expire"
else
expires_s=$( date -jf "%F" "+%s" "${expires}" )
now_s=$( date "+%s" )
expire_int_d=$(( ( ${expires_s} - ${now_s} ) / 86400 ))
exp_min=$(( 1 * 365 ))
exp_max=$(( 3 * 365 + 1 ))
if [ ${expire_int_d} -lt ${exp_min} ]; then
badkey "Key $key expires in less than 1 year ($expire_int_d days)"
fi
if [ ${expire_int_d} -gt ${exp_max} ]; then
badkey "Key $key expires in more than 3 years ($expire_int_d days)"
fi
fi
if [ -z "${key_problems}" ]; then
echo " key okay, ${key} meets minimal requirements" >&2
else
exitstatus=1
echo " ** problems found:" >&2
echo "${key_problems}" >&2
echo " ** key ${key} should not be used!"
fi
echo
done
exit ${exitstatus}