File gpg-offline.gopts of Package gpg-offline
#!/bin/bash
# Trusted GPG offline keyring manipulation tool
# Copyright (C) 2012 Stanislav Brabec <sbrabec@suse.cz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
@genopts
@code_style line_up function_keyword tabs casetab
@program gpg-upstream-check
@version 0.1
@year 2012
@author Stanislav Brabec, SUSE Linux
@bugsto Stanislav Brabec <sbrabec@suse.cz>
@free_copy
@usage [OPTION]... [ARGUMENT]...
@short
Trusted GPG offline keyring manipulation tool
Offline verify files in packages that they are signed by selected signatures.
Manipulate selected signatures in keyring.
@option PACKAGE -p --package=PACKAGE
specifies package name (i. e. file name without suffix, equivalent to --keyring="${DIR:-$PWD}/$PACKAGE.keyring")
@option DIRECTORY --directory=DIR
--package searches for keyring in DIR
@option FILE -f --keyring=FILE
specifies keyring file
@switch ADD -a --add
Adds keys specified in ARGUMENT for inclusion to the package trusted keyring
(run in the source directory)
@switch DELETE -d --delete
Deletes keys specified in ARGUMENT from the package trusted keyring
(run in the source directory)
@switch REVIEW -r --review
reviews the keyring and its human readable corresponds with the contents
@switch REFRESH -R --refresh
refreshes the keyring and its human readable corresponds with the contents
@switch LIST -l --list
lists keyring contents (exactly equal to --review --offline)
@switch VERIFY -c --verify
verifies selected signatures files
@switch OFFLINE -O --offline
does not verify up-to-date status online (use with --add, --review or --refresh)
@cntswitch VERBOSE -v --verbose
be verbose
@end
shopt -s nullglob
# vvrun comment command args
# verbose level 0: run command and redirect stderr to /dev/null
# verbose level 1: run command
# verbose level >=2: echo and run command
function vrun2 {
if $OPTARG_VERBOSE ; then
if test $OPTCNT_VERBOSE -gt 1 ; then
echo >&2 -e "\\n$1"
shift
echo >&2 " $*"
else
shift
fi
"$@"
else
shift
"$@" 2>/dev/null
fi
}
# vvrun comment command args
# verbose level <2: run command
# verbose level >=2: echo and run command
function vvrun {
if $OPTARG_VERBOSE ; then
if test $OPTCNT_VERBOSE -gt 1 ; then
echo >&2 -e "\\n$1"
shift
echo >&2 " $*"
else
shift
fi
else
shift
fi
"$@"
}
function temp_setup {
TEMP=~/.gpg-offline/
# Note: we use ~/.gnupg to prevent problems inside osc generated paths containing ":".
rm -rf ${TEMP}key.$$ ${TEMP}keyring.$$ ${TEMP}keyringdesc.$$ ${TEMP}keyringdesc.no-expired-string.$$ ~/.gnupg/gpg-offline.$$*
mkdir -p ~/.gpg-offline
trap "eval rm -rf ${TEMP}key.$$ ${TEMP}keyring.$$ ${TEMP}keyringdesc.$$ ${TEMP}keyringdesc.no-expired-string.$$ ~/.gnupg/gpg-offline.$$* \$TEMP_FILES ; rmdir --ignore-fail-on-non-empty ~/.gpg-offline" EXIT
}
# keyring_add keyring_op keyring_from keyring_to
# Add command line arguments to keyring_from and create keyring_to
# keyring_from file can be missing
# keyring_from can be equal to keyring_to
function keyring_op {
temp_setup
if test -f $2 ; then
vrun2 "Import existing keyring to the temporary keyring:"\
gpg --no-default-keyring --keyring gpg-offline.$$ --import <$2
fi
keyring_op_$1
vvrun "Export the keyring in ASCII form:"\
gpg --no-default-keyring --keyring gpg-offline.$$ --armor --export-options no-export-attributes,export-clean,export-minimal --export >${TEMP}keyring.$$
# Set locale to C for byte-to-byte reproducibility, but keep UTF-8 CTYPE to get international characters readable.
LC_ALL= LANG=C LC_CTYPE=en_US.UTF-8 vvrun "List the human readable contents of the keyring:"\
gpg --no-default-keyring --list-options show-unusable-uids,show-unusable-subkeys --keyring gpg-offline.$$ --list-keys |
sed '1,/^--/d' >${TEMP}keyringdesc.$$
# Make sure that description is time independent. Convert "expired" to "expires".
sed 's/ \[expired/ [expires/' <${TEMP}keyringdesc.$$ >${TEMP}keyringdesc.no-expired-string.$$
vvrun "Create new keyring and prepare spec:"\
cat ${TEMP}keyringdesc.no-expired-string.$$ ${TEMP}keyring.$$ >$3
}
# keyring_op: Add keys specified in the command line arguments.
function keyring_op_add {
TEMP_FILES="${TEMP}key.$$"
if $OPTARG_OFFLINE ; then
AUTO_KEY_RETRIEVE="no-"
else
AUTO_KEY_RETRIEVE=""
fi
vvrun "Extract minimal form of the key $ID in binary form:"\
gpg --keyserver-options=${AUTO_KEY_RETRIEVE}auto-key-retrieve --armor --export-options no-export-attributes,export-clean,export-minimal --export "${ARGV[@]}" >${TEMP}key.$$
vvrun "Import the new key to the temporary keyring:"\
gpg --no-default-keyring --keyring gpg-offline.$$ --import <${TEMP}key.$$
}
function keyring_op_delete {
TEMP_FILES=""
vvrun "Delete specified keys from the temporary keyring:"\
gpg --no-default-keyring --keyring gpg-offline.$$ --delete-keys "${ARGV[@]}"
}
function keyring_op_review {
TEMP_FILES="${TEMP}review.$$ ${TEMP}keyringdesc.no-expire-info.$$ ${TEMP}keyringdesc.extracted.no-expire-info.$$ ${TEMP}keyringdesc.extracted.$$"
if ! $OPTARG_OFFLINE ; then
vvrun "Refreshing keys from the key server:"\
gpg --trust-model=always --no-default-keyring --keyring gpg-offline.$$ --refresh-keys
fi
}
function filespec_required {
if $OPTARG_FILE ; then
KEYRING="$OPTVAL_FILE"
else
if $OPTARG_PACKAGE ; then
if $OPTARG_DIRECTORY ; then
KEYRING="$OPTVAL_DIRECTORY/$OPTVAL_PACKAGE.keyring"
else
KEYRING="$OPTVAL_PACKAGE.keyring"
fi
else
echo >&2 "$0: You must specify either --package or --file to use this command."
exit 1
fi
fi
}
function keyring_required {
if ! test -f "$KEYRING" ; then
echo >&2 "$0: Keyring \"$KEYRING\" not found."
exit 1
fi
}
if $OPTARG_ADD ; then
filespec_required
if test -f "$KEYRING" ; then
SPEC_MODIFY=false
else
SPEC_MODIFY=true
fi
keyring_op add "$KEYRING" "$KEYRING"
RC=$?
if $SPEC_MODIFY ; then
echo -e "\\nIf not yet done, please add following lines to $OPTVAL_PACKAGE.spec and submit:\\n"
echo "Source2: %{name}.keyring"
echo "BuildRequires: gpg-offline"
echo ""
echo "And in %prep section:"
echo ""
echo "%gpg_verify %{S:1}"
echo ""
echo "(where %{S:1} is the signature)"
echo "
By submitting this change, you certify, that you verified, that the
submitted signature is an original signing key used for the upstream
project. You should do it by comparing the signing key with the web
page, or even better, by checking the project time line and checking,
that the same key is consistently used for longer time."
fi
exit $RC
fi
if $OPTARG_DELETE ; then
filespec_required
keyring_required
keyring_op delete "$KEYRING" "$KEYRING"
exit $?
fi
if $OPTARG_VERIFY ; then
filespec_required
keyring_required
rm -rf ~/.gnupg/gpg-offline.$$*
trap "rm -rf ~/.gnupg/gpg-offline.$$*" EXIT
vvrun "Import armored $KEYRING to the temporary keyring:"\
gpg --pgp2 --no-default-keyring --keyring gpg-offline.$$ --import <"$KEYRING"
# "--trust-model=always" always generates warning "Using untrusted key!". "--quiet" suppresses it.
if ! vvrun "Verifying $SIGNATURE against the temporary keyring only:"\
gpg --pgp2 --quiet --trust-model=always --keyserver-options=no-auto-key-retrieve --no-default-keyring --keyring=gpg-offline.$$ --verify "${ARGV[@]}" ; then
exit 1
fi
exit 0
fi
if test $OPTARG_REVIEW -o $OPTARG_REFRESH -o $OPTARG_LIST ; then
if $OPTARG_LIST ; then
OPTARG_OFFLINE=true
fi
filespec_required
keyring_required
if $OPTARG_REFRESH ; then
REVIEW="$KEYRING.new"
else
TEMP=~/.gpg-offline/
REVIEW=${TEMP}review.$$
fi
temp_setup
keyring_op review "$KEYRING" "$REVIEW"
cat ${TEMP}keyringdesc.$$
if cmp -s "$KEYRING" "$REVIEW" ; then
# Keyrings are bit-to-bit equal. Everything is OK.
if $OPTARG_REFRESH ; then
echo >&2 -e "$KEYRING is already up to date and needs no refresh."
else
if ! $OPTARG_LIST ; then
echo >&2 -e "$KEYRING is a valid armored GPG keyring\\nand the human readable description corresponds to its contents."
fi
fi
rm "$REVIEW"
exit 0
else
# Keyrings are different.
sed '/^-----BEGIN PGP PUBLIC KEY BLOCK-----$/,$d' <"$KEYRING" >${TEMP}keyringdesc.extracted.$$
sed 's/ \[\(expire\|revoked\)[^]]*\]$//;s/^\(uid *\)\[ revoked\]/\1 /' <${TEMP}keyringdesc.extracted.$$ >${TEMP}keyringdesc.extracted.no-expire-info.$$
sed 's/ \[\(expire\|revoked\)[^]]*\]$//;s/^\(uid *\)\[ revoked\]/\1 /' <${TEMP}keyringdesc.no-expired-string.$$ >${TEMP}keyringdesc.no-expire-info.$$
if cmp -s ${TEMP}keyringdesc.extracted.no-expire-info.$$ ${TEMP}keyringdesc.no-expire-info.$$ ; then
# It seems that the author only extended the signature validity or revoked.
echo >&2 -e "ERROR: $KEYRING is a valid armored GPG keyring\\nand the human readable description corresponds to its contents,\\nbut there is a validity info update."
else
echo >&2 -e "ERROR: $KEYRING is a valid armored GPG keyring,\\nbut the the human readable description does not correspond to its contents.\\nIt could be only a cosmetic change, but it may also indicate malicious keyring."
fi
diff ${TEMP}keyringdesc.extracted.$$ ${TEMP}keyringdesc.no-expired-string.$$
if $OPTARG_REFRESH ; then
# We do not force-perform this action. There may be race condition change of upstream keyring between --review and --refresh.
echo >&2 -e "If you really want to accept these changes, please finish it by call:\\nmv $REVIEW $KEYRING"
else
echo >&2 -e "If you are sure that it is OK, and you can perform keyring change,\\nplease call:\\n$0 -f $KEYRING --refresh\\nand then follow hints."
fi
# We always return 1 here. Offline tests should never have problem with revocation or key expiration change,
# online tests should consider it as an error.
exit 1
fi
fi
echo "$0: No operation specified."
exit 1