Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-doc
Path: blob/main/documentation/tools/checkkey.sh
18081 views
1
#!/bin/sh
2
#
3
# $FreeBSD$
4
#
5
6
#
7
# This script is intended to sanity-check PGP keys used by folks with
8
# @FreeBSD.org email addresses. The checks are intended to be derived
9
# from the information at
10
# <https://riseup.net/en/security/message-security/openpgp/gpg-best-practices#openpgp-key-checks>
11
#
12
13
# make sure we are in a well known language
14
LANG=C
15
16
\unalias -a
17
18
progname=${0##*/}
19
20
# Print an informational message
21
info() {
22
echo "$@" >&2
23
}
24
25
# Print a warning message
26
warning() {
27
echo "WARNING: $@" >&2
28
}
29
30
# Print an error message and exit
31
error() {
32
echo "ERROR: $@" >&2
33
exit 1
34
}
35
36
# Print usage message and exit
37
usage() {
38
echo "usage: ${progname} [user] [keyid ...]\n" >&2
39
exit 1
40
}
41
42
# Look for gpg
43
gpg=$(which gpg)
44
if [ $? -gt 0 -o -z "${gpg}" -o ! -x "${gpg}" ] ; then
45
error "Cannot find gpg"
46
fi
47
48
# Set up our internal default gpg invocation options
49
_gpg() {
50
${gpg} \
51
--display-charset utf-8 \
52
--no-greeting \
53
--no-secmem-warning \
54
--keyid-format long \
55
--list-options no-show-uid-validity \
56
"$@"
57
}
58
59
# Look up key by key ID
60
getkeybyid() {
61
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
62
'$5 ~ /^\([0-9A-F]{8}\)?'"$1"'$/i && $12 ~ /ESC/ { print $5 }'
63
}
64
65
# Look up key by email
66
getkeybyemail() {
67
_gpg --with-colons --list-keys "$1" 2>/dev/null | awk -F: \
68
'$10 ~ /<'"$1"'>/i && $12 ~ /ESC/ { print $5 }'
69
}
70
71
# The first command-line argument can be a user name or a key ID.
72
if [ $# -gt 0 ] ; then
73
id="$1"
74
shift
75
else
76
id=$(id -nu)
77
warning "No argument specified, calculating user ID"
78
fi
79
80
# Now let's try to figure out what kind of thing we have as an ID.
81
# We'll check for a keyid first, as it's readily distinguishable
82
# from other things, but if we see that we have one, we push it back
83
# onto the argument list for later processing (because we may have
84
# been given a list of keys).
85
if echo "${id}" | egrep -q '^[0-9A-F]{16}$'; then
86
id_type="keyid"
87
set -- "${id}" $@
88
elif echo "${id}" | egrep -q '^[0-9A-F]{8}$'; then
89
id_type="keyid"
90
set -- "${id}" $@
91
elif echo "${id}" | egrep -iq '^[0-9a-z][-0-9a-z_]*@([-0-9a-z]+\.)[-0-9a-z]+$'; then
92
id_type="email"
93
email="${id}"
94
elif echo "${id}" | egrep -iq '^[0-9a-z][-0-9a-z_]*$'; then
95
id_type="login"
96
login="${id}"
97
email="${id}@FreeBSD.org"
98
else
99
error "Cannot recognize type of ${id} (keyid, login, or email)"
100
fi
101
102
if [ $# -ne 0 ] ; then
103
# Verify the keys that were specified on the command line
104
for arg ; do
105
case $(expr "${arg}" : '^[0-9A-Fa-f]\{8,16\}$') in
106
8)
107
warning "${arg}: recommend using 16-digit keyid"
108
;;
109
16)
110
;;
111
*)
112
warning "${arg} does not appear to be a valid key ID"
113
continue
114
;;
115
esac
116
keyid=$(getkeybyid "${arg}")
117
if [ -n "${keyid}" ] ; then
118
keyids="${keyids} ${keyid}"
119
else
120
warning "${arg} not found"
121
fi
122
done
123
else
124
# Search for keys by freebsd.org email
125
keyids=$(getkeybyemail "${email}")
126
case $(echo "${keyids}" | wc -w) in
127
0)
128
error "no keys found for ${email}"
129
;;
130
1)
131
;;
132
*)
133
warning "Multiple keys found for <${email}>; checking all."
134
warning "If this is not what you want, specify a key ID" \
135
"on the command line."
136
;;
137
esac
138
fi
139
140
# :(
141
if [ -z "${keyids}" ] ; then
142
error "no valid keys were found"
143
fi
144
145
# add a problem report to the list of problems with this key
146
badkey() {
147
key_problems=" ${key_problems}$@
148
"
149
}
150
151
exitstatus=0
152
153
# Check the keys
154
for key in ${keyids} ; do
155
# no problems found yet
156
key_problems=""
157
158
IFS_save="${IFS}"
159
key_info=$( ${gpg} --no-secmem-warning --export-options export-minimal --export ${key} \
160
| ${gpg} --no-secmem-warning --list-packets )
161
# primary keys should be RSA or DSA-2
162
IFS=""
163
version=$( echo $key_info | \
164
awk '$1 == "version" && $3 == "algo" {sub(",", "", $2); print $2; exit 0}' )
165
IFS="${IFS_save}"
166
if [ $version -lt 4 ]; then
167
badkey "This key is a deprecated version $version key!"
168
fi
169
170
IFS=""
171
algonum=$( echo $key_info | \
172
awk '$1 == "version" && $3 == "algo" {sub(",", "", $4); print $4; exit 0}' )
173
IFS="${IFS_save}"
174
case ${algonum} in
175
"1") algo="RSA" ;;
176
"17") algo="DSA" ;;
177
"18") algo="ECC" ;;
178
"19") algo="ECDSA" ;;
179
"22") algo="EDDSA" ;;
180
*) algo="*UNKNOWN*" ;;
181
esac
182
183
IFS=""
184
bitlen=$( echo $key_info | \
185
awk -F : '$1 ~ "pkey" { gsub("[^0-9]*","", $2); print $2; exit 0}' )
186
IFS="${IFS_save}"
187
echo "key ${key}: ${algo}, ${bitlen} bits"
188
case ${algo} in
189
RSA) ;;
190
DSA) if [ "${bitlen}" -le 1024 ]; then \
191
badkey "DSA, but not DSA-2"; \
192
fi ;;
193
EDDSA) ;;
194
*) badkey "non-preferred algorithm"
195
esac
196
197
# self-signatures must not use MD5 or SHA1
198
IFS=""
199
sig_algonum=$( echo $key_info | \
200
awk '$1 == "digest" && $2 == "algo" {sub(",", "", $3); print $3; exit 0}' )
201
IFS="${IFS_save}"
202
case sig_algonum in
203
1) sigs="MD5";;
204
2) sigs="SHA1";;
205
3) sigs="RIPEMD160";;
206
8) sigs="SHA256";;
207
9) sigs="SHA384";;
208
10) sigs="SHA512";;
209
11) sigs="SHA224";;
210
*)
211
esac
212
for sig in ${sigs}; do
213
if [ "${sig}" = "MD5" -o "${sig}" = "SHA1" ]; then
214
badkey "self-signature ${sig}"
215
fi
216
done
217
218
# digest algo pref must include at least one member of SHA-2
219
# at a higher priority than both MD5 and SHA1
220
IFS=""
221
algopref=$( echo $key_info | \
222
awk -F : '$1 ~ "pref-hash-algos" {gsub("[^ 0-9]", "", $2); print $2; exit 0}' )
223
IFS="${IFS_save}"
224
# if 3, 2, or 1 are before 11, 10, 9, or 8, then
225
set -- ${algopref}
226
if [ $1 -lt 4 ]; then
227
badkey "algorithm prefs do not have SHA-2 higher than MD5 or SHA1"
228
fi
229
230
# primary keys should have an expiration date at least a year
231
# in the future to make them worth committing, but no more
232
# than three years in the future
233
expires=$( _gpg --list-keys ${key} | \
234
awk "/$keyid .*expires:/ {sub(\"[^-0-9]\", \"\", \$NF); print \$NF; exit 0}" )
235
if [ -z "${expires}" ]; then
236
badkey "this key does not expire"
237
else
238
expires_s=$( date -jf "%F" "+%s" "${expires}" )
239
now_s=$( date "+%s" )
240
# 86400 == # seconds in a normal day
241
expire_int_d=$(( ( ${expires_s} - ${now_s} ) / 86400 ))
242
exp_min=$(( 1 * 365 )) # Min expiry time is 1 year
243
exp_max=$(( 3 * 365 + 1 )) # Max expiry time is 3 years
244
# We add 1 day because in a 3-year
245
# period, probability of a leap day
246
# is 297/400, about 0.74250
247
if [ ${expire_int_d} -lt ${exp_min} ]; then
248
badkey "Key $key expires in less than 1 year ($expire_int_d days)"
249
fi
250
if [ ${expire_int_d} -gt ${exp_max} ]; then
251
badkey "Key $key expires in more than 3 years ($expire_int_d days)"
252
fi
253
fi
254
255
# report problems
256
if [ -z "${key_problems}" ]; then
257
echo " key okay, ${key} meets minimal requirements" >&2
258
else
259
exitstatus=1
260
echo " ** problems found:" >&2
261
echo "${key_problems}" >&2
262
echo " ** key ${key} should not be used!"
263
fi
264
echo
265
done
266
exit ${exitstatus}
267
268