#!/bin/bash # git-verify-to-tip # ----------------- # # Verify PGP signatures on all commits from the last signed tag # or any arbitrary object in the repository history. # # This script can be installed as hooks/pre-push. # # Configurable parameters # ----------------------- # We always ensure the signing key is both GOOD and VALID, which means # that the keys you are checking against should be imported into your # gnupghome and signed by a trusted key (e.g. your own). If you want to # use a different GNUPG directory other than the one in your home, you # can "export GNUPGHOME=some/path" before running this script. In addition, # you may explicitly specify the keys to trust in the ONLYKEYS parameter below, # e.g. if you want to make sure the signatures came from a very small subset # of developers. Pipe-separate multiple keys, e.g.: # ONLYKEYS="ABAF11C65A2970B130ABE3C479BE3E4300411886|647F28654894E3BD457199BE38DBBDC86092693E" ONLYKEYS= # # By default, we check signatures on every commit, but if you set this to # --merges, we will only check signatures on merges. You can also add any # other flags accepted by git-rev-list. REVFLAGS= # # When set to "" we start from the latest annotated tag we find. # You can also list an arbitrary commit object here. # When running as hooks/pre-push, we ignore this entirely and use the # commit information provided by git on stdin. STARTFROM= # # We can also get these parameters from the git config. E.g.: # [verify-to-tip] # onlykeys = ABAF11C65A2970B130ABE3C479BE3E4300411886|647F28654894E3BD457199BE38DBBDC86092693E # revflags = --merges # startfrom = abcdef123456 # if [[ -z ${ONLYKEYS} ]]; then ONLYKEYS=$(git config --get verify-to-tip.onlykeys) fi if [[ -z ${REVFLAGS} ]]; then REVFLAGS=$(git config --get verify-to-tip.revflags) fi if [[ -z ${STARTFROM} ]]; then STARTFROM=$(git config --get verify-to-tip.startfrom) fi # End configuration function verify_raw_gpg { # We are looking for [GNUPG:] GOODSIG and [GNUPG:] VALIDSIG # They must be both present, or this is not a valid sig COUNT=$(echo "${1}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)') if [[ ${COUNT} -lt 2 ]]; then return 1 fi if [[ -z ${ONLYKEYS} ]]; then return 0 fi if $(echo "${1}" | grep -q -E "^\[GNUPG:\] VALIDSIG .* (${ONLYKEYS})\$"); then return 0 fi return 1 } function verify_rev_range { REVRANGE=${1} REVFLAGS=${2} for REV in $(git rev-list ${REVRANGE} ${REVFLAGS}); do echo "Verifying $REV" RAWOUT=$(git verify-commit --raw ${REV} 2>&1) if ! verify_raw_gpg "${RAWOUT}"; then echo "CRITICAL: ${REV} signature did NOT verify:" echo "${RAWOUT}" return 1 fi done return 0 } # Are we running from hooks/pre-push? If so, $1 and $2 should be set. if [[ -z "${1}${2}" ]]; then # Not running as a pre-push hook. if [[ -z ${STARTFROM} ]]; then # verify the last annotated tag STARTFROM=$(git describe --abbrev=0) RAWOUT=$(git verify-tag --raw ${STARTFROM} 2>&1) else # verify the arbitrary commit provided RAWOUT=$(git verify-commit --raw ${STARTFROM} 2>&1) fi echo "Verifying ${STARTFROM}" if ! verify_raw_gpg "${RAWOUT}"; then echo "CRITICAL: ${STARTFROM} signature did NOT verify:" echo "${RAWOUT}" exit 1 fi REVRANGE="${STARTFROM}.." if ! verify_rev_range "${REVRANGE}" "${REVFLAGS}"; then exit 1 fi else # We are in a pre-push hook Z40="0000000000000000000000000000000000000000" while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do if [[ ${LOCAL_SHA} == ${Z40} ]]; then # Ignore delete continue fi if [[ ${REMOTE_SHA} == ${Z40} ]]; then # New branch, examine all commits REVRANGE=${LOCAL_SHA} else # Update to existing branch, examine new commits REVRANGE="${REMOTE_SHA}..${LOCAL_SHA}" fi if ! verify_rev_range "${REVRANGE}" "${REVFLAGS}"; then exit 1 fi done fi echo "Verified successfully." exit 0