/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	#---------------------------------------------------------------------