/var/lib/sorcery/modules/libgpg
1 #!/bin/bash
2 #---------------------------------------------------------------------
3 ##
4 ## @Synopsis Functions that verify gpg signatures
5 ##
6 ## @Copyright Copyright 2005 by the Source Mage Team
7 ##
8 #---------------------------------------------------------------------
9
10
11
12 #---------------------------------------------------------------------
13 ## Low level routine for verifying a file given a signature and keyring.
14 ## The keyring must contain the public key for the signature.
15 ## @param signature of the file
16 ## @param file to verify
17 ## @param public keyring
18 ##
19 ## @return 0 on success, non-zero on failure:
20 ## @return 1 verification failure
21 ## @return 3 no signature file
22 ## @return 4 no file to verify
23 ## @return 5 no keyring
24 ## @return 200 gpg isnt installed
25 ##
26 ## @stdout message when gpg is not installed
27 ##
28 #---------------------------------------------------------------------
29 function gpg_verify_signature() { # $1 sig $2 file $3 pubring $4 algo var
30
31 local signature=$1
32 local file=$2
33 local keyring=$3
34 local rc
35 local algo
36
37 test -f $signature || return 3
38 test -f $file || return 4
39 test -f $keyring || return 5
40
41 # always trust and supply our own keyring.
42 # We provide our own trust for the pubkey validity.
43
44 local GPGPROG
45 smgl_which gpg GPGPROG 2> /dev/null
46
47 if test -z "$GPGPROG" ; then
48 message "It appears you do not have gpg (gnupg) in your PATH."
49 message "${QUERY_COLOR}For full source verification, it is highly" \
50 "suggested that you cast gnupg\nas soon as possible. This" \
51 "should be done for you on a system update.${DEFAULT_COLOR}"
52 return 200
53 else
54 local output=$TMP_DIR/$(smgl_basename $file).gpgout
55 LC_ALL=C gpg --no-default-keyring \
56 --always-trust \
57 --keyring $keyring \
58 --batch \
59 --verbose \
60 --verify \
61 $signature \
62 $file 2> $output
63 rc=$?
64 if [[ $rc != 0 ]] ; then
65 cat $output
66 return $rc
67 fi
68 algo=$(awk '/digest algorithm/ { print tolower($NF) }' $output | sort -u)
69 rm $output &>/dev/null
70 fi
71 [ ! -z "$4" ] && eval "$4=\"$algo\""
72 return 0
73 }
74
75 #---------------------------------------------------------------------
76 ## Get the sorcery gpg key file associated with a branch
77 ## @param (optional) sorcery branch, if empty use $SORCERY_BRANCH
78 ## @return 0 on success, 1 on failure
79 ## @stdout full path to sorcery key (if successful)
80 #---------------------------------------------------------------------
81 function gpg_get_sorcery_key() {
82 local branch=${1:-$SORCERY_BRANCH}
83 local key=$GPG_KEY_DIR/sorcery-$branch.gpg
84 test -f $key || return 1
85 echo $key
86 return 0
87 }
88
89 #---------------------------------------------------------------------
90 ## Get the grimmoire gpg key file associated with a branch
91 ## @param grimoire branch (test, stable, games etc.)
92 ## @return 0 on success, 1 on failure
93 ## @stdout full path to grimoire key (if successful)
94 #---------------------------------------------------------------------
95 function gpg_get_grimoire_key() {
96 local branch=$1
97 local key=$GPG_KEY_DIR/grimoire-$branch.gpg
98 test -f $key || return 1
99 echo $key
100 return 0
101 }
102
103 #---------------------------------------------------------------------
104 ## Verify a grimoire tarball's gpg signature
105 ## @param file on local disk to verify
106 ## @param url from which to get the signature
107 ## @param (optional) grimoire branch, if empty derive it from the filename with ${SOURCE%%.*}
108 ## @param (optional) signature file, if empty download $SOURCE.$GPG_SIG_EXT from $2
109 ## @return 0 on success, non-zero on failure:
110 ## @return 1 verification failed
111 ## @return 201 verification is disabled
112 ## @return 254 no keyring found
113 ## @return 255 could not download signature
114 ## @return anything else see gpg_verify_signature
115 ##
116 ## @stdout possibly a failure message depending on what happens (nothing is output on success)
117 #---------------------------------------------------------------------
118 function gpg_verify_grimoire() {
119 if [[ "$GPG_VERIFY_GRIMOIRE" != on ]] ; then
120 return 201
121 fi
122
123 local FILENAME=$1
124 local SOURCE_URL=$2
125
126
127 # optional args
128 local BRANCH=$3
129 local SIGNATURE=$4
130
131 local SOURCE
132 smgl_basename "$FILENAME" SOURCE
133
134 if [[ -z $BRANCH ]]; then
135 # deduce the name of the branch from the tarball (stable-rc-0.19.tar.bg2)
136 # asumes double extension (like .tar.gz)
137 # asumes no numbers preceded by a dash in the name
138 # asumes dash as the delimiter between the name and the optional version
139 # asumes version starts with a number
140 BRANCH=${SOURCE%.*}
141 BRANCH=${BRANCH%.*}
142 BRANCH=${BRANCH%-[0-9]*}
143 fi
144
145 local gpg_pub_key=$(gpg_get_grimoire_key $BRANCH)
146 if test -z $gpg_pub_key && test -f $gpg_pub_key ; then
147 message "No keyring found! (maybe you need to cast sorcery-pubkeys?)"
148 return 254
149 fi
150 gpg_verify_common $FILENAME $SOURCE_URL $gpg_pub_key grimoire $SIGNATURE
151 }
152
153
154
155 function verify_grimoire_tree() {
156 local grimoire_name=$1
157 local grimoire_dir=$2
158
159 if [[ "$GPG_VERIFY_GRIMOIRE" != on ]] ; then
160 return 253
161 fi
162
163 if ! list_find "$GPG_GRIMOIRE_LIST" $grimoire_name &> /dev/null; then
164 message "${MESSAGE_COLOR}The grimoire (tree)" \
165 "${SPELL_COLOR}$grimoire_name${DEFAULT_COLOR}" \
166 "${MESSAGE_COLOR}is not an official grimoire and for such" \
167 "${PROBLEM_COLOR}there is no verification method!$DEFAULT_COLOR"
168 query "Continue anyway?" y
169 return $?
170 fi
171
172 local gpg_pub_key=$(gpg_get_grimoire_key $grimoire_name)
173 if test -z $gpg_pub_key && test -f $gpg_pub_key ; then
174 message "No keyring found! (maybe you need to cast sorcery-pubkeys?)"
175 return 254
176 fi
177
178 manifest=$grimoire_name.manifest.$GRIMOIRE_MANIFEST_ALGORITHM
179 manifest_url=${CODEX_MANIFEST_URL}/$manifest
180
181 pushd $TMP_DIR >/dev/null || return 1
182
183 local manifest_target manifest_type
184 url_download $manifest $manifest_url "" manifest_target manifest_type
185 #check the success of the download
186 if [ $? != 0 ] || [[ "$manifest_type" != file ]] ; then
187 message "Error downloading manifest..."
188 return 1
189 fi
190
191 gpg_verify_common $manifest $CODEX_MANIFEST_URL $gpg_pub_key "grimoire manifest"
192 if ! gpg_user_query $?; then
193 return 2
194 fi
195
196 popd >/dev/null
197
198 verify_grimoire_against_manifest "$grimoire_dir" "$TMP_DIR/$manifest" \
199 "$GRIMOIRE_MANIFEST_ALGORITHM"
200 }
201
202 #---------------------------------------------------------------------
203 ## Verify a sorcery tarball's gpg signature
204 ## @param file on local disk to verify
205 ## @param url from which to get the signature
206 ## @param (optional) signature file, if empty download $SOURCE.$GPG_SIG_EXT from $2
207 ##
208 ## @return 0 on success, non-zero on failure:
209 ## @return 1 verification failed
210 ## @return 2 verification is disabled
211 ## @return 254 no keyring found
212 ## @return 255 could not download signature
213 ## @return anything else see gpg_verify_signature
214 ##
215 ## @stdout possibly a failure message depending on what happens (nothing is output on success)
216 #---------------------------------------------------------------------
217 function gpg_verify_sorcery() {
218 if [[ "$GPG_VERIFY_SORCERY" != on ]] ; then
219 return 201
220 fi
221
222 local FILENAME=$1
223 local SOURCE_URL=$2
224
225 # optional args
226 local SIGNATURE=$3
227
228 local gpg_pub_key=$(gpg_get_sorcery_key)
229 if test -z $gpg_pub_key && test -f $gpg_pub_key ; then
230 message "No keyring found! (maybe you need to cast sorcery-pubkeys?)"
231 return 254
232 fi
233 gpg_verify_common "$FILENAME" "$SOURCE_URL" "$gpg_pub_key" "sorcery" "$SIGNATURE"
234 }
235
236 #---------------------------------------------------------------------
237 ## Common code for verifying sorcery/grimoire tarballs
238 ## @param file on local disk to verify
239 ## @param url from which to get the signature
240 ## @param keyring to verify with
241 ## @param grimoire or sorcery, whatever it is thats being verified (used in an output message
242 ## @param (optional) signature file, if empty download $SOURCE.$GPG_SIG_EXT from $2
243 ##
244 ## @return 0 on success, non-zero on failure:
245 ## @return 1 verification failed
246 ## @return 255 could not download signature
247 ## @return anything else see gpg_verify_signature
248 ##
249 ## @stdout possibly a failure message depending on what happens (nothing is output on success)
250 #---------------------------------------------------------------------
251 function gpg_verify_common() {
252 # download the signature
253 local FILENAME=$1
254 local SOURCE_URL=$2
255 local KEYRING=$3
256 local REASON=$4
257 local SIGNATURE=$5
258 local SOURCE
259 smgl_basename "$FILENAME" SOURCE
260
261 local SIG_FILE=${SOURCE}.${GPG_SIG_EXT}
262 pushd $TMP_DIR &>/dev/null ||
263 { message "Failed to cd to $TMP_DIR!!"; return 2;}
264 if test -z "$SIGNATURE" ; then
265 local SIG_URL=$SOURCE_URL/$SIG_FILE
266 local gpg_target gpg_type
267 url_download "$SIG_FILE" "$SIG_URL" "file" gpg_target gpg_type &&
268 [[ $gpg_type == file ]] ||
269 {
270 message "Failed to get gpg signature! Verification is impossible!"
271 return 255
272 }
273 [[ "$gpg_target" != $SIG_FILE ]] && mv "$gpg_target" "$SIG_FILE"
274 else
275 cp $SIGNATURE $TMP_DIR/$SIG_FILE
276 fi
277
278 gpg_verify_signature $TMP_DIR/$SIG_FILE $FILENAME $gpg_pub_key
279 rc=$?
280 rm $TMP_DIR/$SIG_FILE
281 popd &>/dev/null
282
283 return $rc
284 }
285
286 #---------------------------------------------------------------------
287 ## Handles interpriting the output of gpg_verify_sorcery or
288 ## gpg_verify_grimoire.
289 ##
290 ## @param return code of gpg_verify_sorcery or gpg_verify_grimoire
291 ## @return 0 if the program should continue
292 ## @return 1 if not
293 ##
294 ## @stdout Some message thats supposed to inform the user of whats
295 ## @stdout going on, or possibly a query asking the user if they want
296 ## @stdout to continue even though gpg verification failed.
297 #---------------------------------------------------------------------
298 function gpg_user_query() {
299 local rc=$1
300 if [[ $rc == 0 ]] ; then
301 message "${MESSAGE_COLOR}gpg signature verified!${DEFAULT_COLOR}"
302 elif [[ $rc == 201 ]] ; then
303 message "${MESSAGE_COLOR}gpg verification is disabled${DEFAULT_COLOR}"
304 else
305 message "${PROBLEM_COLOR}Failure to verify gpg signature${DEFAULT_COLOR}"
306 if does_spell_need_update sorcery-pubkeys; then
307 message "It looks like casting sorcery-pubkeys may help">help">help."
308 fi
309 case "$3" in
310 grimoire)
311 if list_find "$GPG_GRIMOIRE_LIST" $2 > /dev/null 2>&1 ; then
312 query "Continue anyway?" n || return 1
313 else
314 # if its not one of our grimoires may want the default to be y
315 query "Continue anyway?" y || return 1
316 fi
317 ;;
318 spell)
319 unpack_file_user_query $rc || return 1
320 ;;
321 *)
322 query "Continue anyway?" n || return 1
323 ;;
324 esac
325 fi
326 return 0
327 }
328
329
330 #---------------------------------------------------------------------
331 ## @param algorithm to use
332 ## @param file to get hashsum of
333 ## @stdout output is exactly the same format as md5sum/sha1sum, just with
334 ## @stdout a different hashsum. "hashsum<space><space>filename". The hashsum is
335 ## @stdout printed with all lowercase letters.
336 ##
337 ## This assumes that the caller has already verified that gpg is
338 ## installed and supports the specified hash function.
339 ##
340 #---------------------------------------------------------------------
341 function gpg_hashsum() {
342 local algorithm=$1
343 local file=$2
344 LC_ALL=C gpg --print-md "$algorithm" "$file"| tr -d '\n ' |cut -f2 -d:|
345 tr 'A-F' 'a-f'|tr -d '\n'
346 echo " $file"
347 }
348
349 #---------------------------------------------------------------------
350 ## @stdout All the hash algorithms supported by gpg, algorithms printed in
351 ## @stdout lower case.
352 ##
353 ## This assumes the caller has already verified that gpg is installed.
354 #---------------------------------------------------------------------
355 function gpg_get_hashes() {
356 LC_ALL=C gpg --version 2> /dev/null | awk -F: '/^Hash/ { gsub(","," ",$2); print tolower($2); }'
357 }
358
359 #---------------------------------------------------------------------
360 ## Verify a tree against a manifest file
361 ## @param directory to verify
362 ## @param manifest file, the format is like what the md5sum tool would produce
363 ## @param algorithm to use, this can be anything supported by gpg
364 ## @param regular expression of files to ignore
365 #---------------------------------------------------------------------
366 function verify_against_manifest() {
367 local dir=$1
368 local manifest=$2
369 local algorithm=$3
370 local ignore=$4
371 local base
372 smgl_basename "$dir" base
373 local real_list=$TMP_DIR/$base
374 local missing=$TMP_DIR/missing.$base
375 local rc=0
376
377 message "Validating tree at $dir with $manifest"
378 local manifest_format=$(echo $manifest|awk -F. '{print $NF}')
379
380 pushd $dir > /dev/null || return $?
381 find . -type f > $real_list
382 { cat $real_list $real_list ; awk '{print $NF}' $manifest ; } |
383 sort | uniq -c | grep -v '^ *3' | grep -v "$ignore" > $missing
384 NO_TREE=$(grep "^ *1" $missing|sed 's/^ *1 //')
385 if [[ $NO_TREE ]] ; then
386 message "${PROBLEM_COLOR}The following exist only in the manifest" \
387 "and are missing from the tree!${DEFAULT_COLOR}"
388 echo "$NO_TREE"|$PAGER
389 let rc+=1
390 fi
391 NO_MANIFEST=$(grep "^ *2" $missing|sed 's/^ *2 //')
392 if [[ $NO_MANIFEST ]] ; then
393 message "${PROBLEM_COLOR}The following exist only in the tree" \
394 "and are missing from the manifest!${DEFAULT_COLOR}"
395 echo "$NO_MANIFEST"|$PAGER
396 let rc+=1
397 fi
398 local hash r
399 while read hashsum file; do
400 local hash=$(gpg_hashsum $algorithm $file 2>/dev/null|cut -f1 -d' ')
401 r=$?
402 if [[ $hash != $hashsum ]] || [[ $r != 0 ]]; then
403 NOT_OK=( $NOT_OK "$file" )
404 fi
405 done < $manifest
406
407 if [[ $NOT_OK ]] ; then
408 message "${PROBLEM_COLOR}The following have bad checksums${DEFAULT_COLOR}"
409 echo "$NOT_OK"
410 let rc+=1
411 fi
412 popd $dir > /dev/null
413 return $rc
414 }
415
416 #---------------------------------------------------------------------
417 ## Verify a grimoire tree and ignore files sorcery adds post-download
418 #---------------------------------------------------------------------
419 function verify_grimoire_against_manifest() {
420 verify_against_manifest $1 $2 $3 \
421 '^ *2 \./\(GRIMOIRE\|codex\.index\|provides\.index\)$'
422 }
423
424 #---------------------------------------------------------------------
425 ## Ask the user what they want to do if verification of a grimoire tree
426 ## fails.
427 #---------------------------------------------------------------------
428 function grimoire_tree_user_query() {
429 local grimoire_name=$1
430 message "${PROBLEM_COLOR}Verification of the grimoire tree" \
431 "${DEFAULT_COLOR}${SPELL_COLOR}$grimoire_name${DEFAULT_COLOR}" \
432 "${PROBLEM_COLOR}failed!${DEFAULT_COLOR}"
433 message "${PROBLEM_COLOR}What would you like to do?${DEFAULT_COLOR}"
434 local choice
435 select_list choice "" "set aside" "remove" "ignore"
436 case "$choice" in
437 "set aside") local tgt=$grimoire_name.$(date +%Y%m%d%H%M).corrupt
438 message "moving grimoire to $tgt"
439 mv $grimoire_name $tgt
440 scribe_remove $grimoire_name &>/dev/null
441 ;;
442 remove) rm -rf $grimoire_name
443 scribe_remove $grimoire_name &>/dev/null;;
444 ignore) return 0 ;;
445 esac
446 return 1
447 }
448
449 #---------------------------------------------------------------------
450 ## This software is free software; you can redistribute it and/or modify
451 ## it under the terms of the GNU General Public License as published by
452 ## the Free Software Foundation; either version 2 of the License, or
453 ## (at your option) any later version.
454 ##
455 ## This software is distributed in the hope that it will be useful,
456 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
457 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
458 ## GNU General Public License for more details.
459 ##
460 ## You should have received a copy of the GNU General Public License
461 ## along with this software; if not, write to the Free Software
462 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
463 #---------------------------------------------------------------------