diff --git a/.sig/manifest.8E47A1EC35A1551D.asc b/.sig/manifest.8E47A1EC35A1551D.asc index 0691eb1..b13eb80 100644 --- a/.sig/manifest.8E47A1EC35A1551D.asc +++ b/.sig/manifest.8E47A1EC35A1551D.asc @@ -1,16 +1,16 @@ -----BEGIN PGP SIGNATURE----- -iQIzBAABCgAdFiEEZ1U/vaRrtxq9LgsLjkeh7DWhVR0FAl+4nEwACgkQjkeh7DWh -VR0HOg/+M33tcLn9pmiE+31ovH+4/XuDfYIuefWrxc/YLS+E8QIvmw5X+cXCuohV -/qtfPg/EHddvMHq6BCMdo4XwqiPhYXbrh25FgT+bG2c8N8SMK797ffGo3DRH2ZbF -yDFlQz0jShSbjvSFDFHYiS75ACKQ1AXN86gHr+/oH4aMiVtNA2AwrFmcajtY/nUN -UmmRW6h/4mFbIp+MhbX9YH9Pc39da7ZvZyO/S6t9jA3M6Kwu5TZGkhPIHmuhnWc4 -CLG+PqQJabtYjt6VniJCcz4uubKKAgmjgB7RgVCQt05wLiB3Ca9MiTWZpGjuhedJ -XrdphEep6cvWa4hvgWvKHQMtJEAKCD21rmnFXkGUl4hbe/joQ1rvEv+JL+QLIZv9 -S8rPeByVM4MtItJGIALL157K7dQdepxneRsGOJam6bI0ZLo3DJyDMODJWLFiHmUt -WlZ2Queqp6UQbs19HJYgNve82wZsX/iXbqSRzg6V+043EDgOHpXX0zbJ6qMQXEd9 -UlJZQE4VPr/4V8LvYzMxbtlEClhAFIjKY2qAGwvkwoQgEcxYZ6fQZz6nriSPmWOO -lFa5QQZEgJtMge9kQqmYAenD4J0F30S2Lj2IcS+t28XtTYyMiQ48cI37PUkuB5BC -LSCx7lUGGLo5+Uz3aJ89yTpmhCXK0XvmCEqA0rPpz30NcWEaJ+Q= -=7Skg +iQIzBAABCgAdFiEEZ1U/vaRrtxq9LgsLjkeh7DWhVR0FAl+9voEACgkQjkeh7DWh +VR1oSg//X/RqCuceOFQdn8HfC24P8mFuHgFFJpdcVDZALLrXek9QuBd++3VSQBPq +ID26OWh0644BOZDNOXkNvYVnyrwc27CAxjWpbQkn8Z+vl/tpO+A7BTSVxS7M70ID +krxi78nZGgatMsZMWzsPvWe8UCzMd4EN92AUhXGCdU2iSSUeHBz4LEJ46QjnAwq9 +dda+mo7qvYLcLcGZJcdrjKTdgquDi83eUkCvB5tI+SJsdRrQED9sEjAQW/rwVs1p +eh1riIJ7ooG0s1SGWT25tn7Z8Xm91mV7GjcbG+PZJUFdcmwouV9lqc0Kzx/6sxvO +veD68TasBcDvn3HfOVrM641HuXVqLPwDWq+eZz8WdhfNj6fKF0u5325XKKfDBIsH +uro3AuE/t1KkAnPcYUQXh3lhG/aY6Xddgy7JJ2ZwaiV6jV17XoNKfstjrEGkBgRc +BZmAX4dtVK6VLcnoZC16DBFicn7EwCKO1eoSJD7n+3kPH9TbcwD9134g3GJDzif/ +n7PGPEtinO0lGRUbtr3QO73eaV2IaGvc/d0e7zzHq1gyaMOycTAxouSe8ePaJVn5 +iQkqSr/dcgHNL7XA/o39V2CWuC2Tx5UtRKJusJNQhM92Sn9lcAfTz1U6L0nZj4uk +SKShEww86RLSj07jGfcgHjEXPQ5WZ5qmizsxJyXOITpR2wV1FmM= +=jgfL -----END PGP SIGNATURE----- diff --git a/.sig/manifest.txt b/.sig/manifest.txt index 4fb7c0d..4e02fb8 100644 --- a/.sig/manifest.txt +++ b/.sig/manifest.txt @@ -1,8 +1,8 @@ 64263feac7b00952e9ec3b6c1fd11316faa58ff673c6bd085fac9f6f8d8389f6 .gitignore 67377eee89dfc4411665474ac0bee0f9a19ea7e594bcc8606b0bc3ace69f0aa1 .gitlab-ci.yml ad3d473c630217dff7c4499efc1de46fc3a55068677c2bb3a21714aa56dd408a Makefile -464ed12795e3e41eee83713709069fefb07f0676ba237894a9325aafe5c91e31 README.md -3dfa934d88199ed8992d63d68bce81c5b82970b4a78d4ccde056d6039ee3cf5f sig +7d6ada59fb41657c84b9fed7984a0e763d58c1fa851bf306db50f686eebdfbdd README.md +e6517003db9161086c31e8fc21184273a1e66d536bf628739eae7118052c9be8 sig 646a6c11ef22d51bd7fadff5ecc806d8d3e7c62151a0dd56bbeb59eca74c671c test/Dockerfile dd79ef0e6d0738321f916a5c85a60d44152fb1ffcd71572de98cf48e0d0d911c test/keys/user1.pub.asc c98a656738f188f650fa0107e3478d640c175a3db481a6c3cbc267f75a05b440 test/keys/user1.sec.asc @@ -14,5 +14,5 @@ c0e3df63b1f01a83e17c463af9e37365a5e38ee0289d59cdfee725df202a311b test/keys/user3 d4cbeffdbf7064aaffe94556b5879c88cddf479e3e76518f25c3491482abd789 test/keys/user4.sec.asc c608e63175a1e9cc3fe2500372769a9e30b808d2e4d4a950796d98dac14775ea test/keys/user5.pub.asc 873f3a1e2da41587f4b5a0dad5d8b704a37144e54931fe3a167ea9648772a5dc test/keys/user5.sec.asc -ab7f531be1e3f9075ee43e20dd230b6400cf856f7dfc857b848c5e9b766fc3f4 test/test.bats +8a517fceb28c7afe5c6baba7792f5f914310800b7ddfde8d3265e26ab46cbee7 test/test.bats 418903b58dad935ee3aa1dfcc4c4ac22fd77a838b87a5c2c3fe4e510a164f0a0 test/test_helper.bash diff --git a/README.md b/README.md index 51517e5..0084b86 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The simple GPG signature toolchain for directories or git repos. ## Usage -* sig verify [-g,--group=] [-t,--threshold=] [-m,--method= ] +* sig verify [-g,--group=] [-t,--threshold=] [-m,--method= ] [-d,--diff=] * Verify m-of-n signatures by given group are present for directory * sig add * Add signature to manifest for this directory @@ -119,7 +119,13 @@ sig verify --threshold 2 #### Verify 3 unique signatures from specified signing group via Git method ``` -sig verify --threshold 2 --group myteam --method git +sig verify --threshold 3 --group myteam --method git +``` + +#### Verify 2 unique signatures via detached method and diff on failure + +``` +sig verify --threshold 2 --diff master --method detached ``` #### Add Detached Signature diff --git a/sig b/sig index fdca2e0..b8a027f 100755 --- a/sig +++ b/sig @@ -8,7 +8,7 @@ readonly MIN_GETOPT_VERSION=2.33 ## Private Functions -### Bail with error message +### Exit with error message die() { echo "$@" >&2 exit 1 @@ -117,7 +117,11 @@ check_tools(){ ### Use git if available, else fall back to find get_files(){ if [ -d '.git' ] && command -v git >/dev/null; then - git ls-files | grep -v ".${PROGRAM}" + git ls-files \ + --cached \ + --others \ + --exclude-standard \ + | grep -v ".${PROGRAM}" else find . \ -type f \ @@ -233,27 +237,33 @@ verify_detached() { for sig_filename in "${filename%.*}".*.asc; do gpg --verify "${sig_filename}" "${filename}" >/dev/null 2>&1 || { echo "Invalid detached signature: ${sig_filename}"; - exit 1; + return 1; } file_fp=$( get_file_fp "${sig_filename}" ) fp=$( get_primary_fp "${file_fp}" ) uid=$( get_uid "${fp}" ) - [[ "${seen_fps}" == *"${fp}"* ]] \ - && die "Duplicate signature: ${sig_filename}"; + [[ "${seen_fps}" == *"${fp}"* ]] && { + echo "Duplicate signature: ${sig_filename}"; + return 1; + } echo "Verified detached signature by \"${uid}\"" if [ ! -z "${group}" ]; then - group_check_fp "${fp}" "${group}" \ - || die "Detached signing key not in group \"${group}\": ${fp}"; + group_check_fp "${fp}" "${group}" || { + echo "Detached signing key not in group \"${group}\": ${fp}"; + return 1; + } fi seen_fps="${seen_fps} ${fp}" ((sig_count=sig_count+1)) done - [[ "${sig_count}" -ge "${threshold}" ]] || \ - die "Minimum detached signatures not found: ${sig_count}/${threshold}"; + [[ "${sig_count}" -ge "${threshold}" ]] || { + echo "Minimum detached signatures not found: ${sig_count}/${threshold}"; + return 1; + } } ### Verify all commits in git repo have valid signatures @@ -263,7 +273,7 @@ verify_git(){ [ $# -eq 2 ] || die "Usage: verify_git " local -r threshold="${1}" local -r group="${2}" - local seen_fps="" sig_count=0 depth=0 + local seen_fps="" sig_count=0 depth=0 ref commit fp uid while [[ $depth != "$(git rev-list --count HEAD)" ]]; do ref=HEAD~${depth} @@ -271,8 +281,10 @@ verify_git(){ fp=$(git log --format="%GP" "$ref" -n1 ) uid=$( get_uid "${fp}" ) - git verify-commit HEAD~${depth} >/dev/null 2>&1\ - || die "Unsigned commit: ${commit}" + git verify-commit HEAD~${depth} >/dev/null 2>&1 || { + echo "Unsigned commit: ${commit}"; + return 1; + } if [[ "${seen_fps}" != *"${fp}"* ]]; then seen_fps="${seen_fps} ${fp}" @@ -281,8 +293,10 @@ verify_git(){ fi if [ ! -z "$group" ]; then - group_check_fp "${fp}" "${group}" \ - || die "Git signing key not in group \"${group}\": ${fp}" + group_check_fp "${fp}" "${group}" || { + echo "Git signing key not in group \"${group}\": ${fp}"; + return 1; + } fi [[ "${sig_count}" -ge "${threshold}" ]] && break; @@ -290,10 +304,75 @@ verify_git(){ ((depth=depth+1)) done - [[ "${sig_count}" -ge "${threshold}" ]] \ - || die "Minimum git signatures not found: ${sig_count}/${threshold}"; + [[ "${sig_count}" -ge "${threshold}" ]] || { + echo "Minimum git signatures not found: ${sig_count}/${threshold}"; + return 1; + } } +get_temp(){ + echo "$( + mktemp \ + --quiet \ + --directory \ + -t "$(basename "$0").XXXXXX" 2>/dev/null \ + || mktemp \ + --quiet \ + --directory + )" +} + +verify_git_diff(){ + [ $# -eq 4 ] \ + || die "Usage: verify_git_diff " + command -v git >/dev/null 2>&1 \ + || die "Error: verify diff requires 'git' which is not installed" + local -r diff_ref=${1} + local -r threshold=${2} + local -r group=${3} + local -r method=${4} + local -r temp_repo=$(get_temp) + local -r git_root=$(git rev-parse --show-toplevel) + local -r curr_ref=$(git rev-parse HEAD) + set -x + cp -a "${git_root}/." "${temp_repo}/" + cd "${temp_repo}" + git reset --hard "${diff_ref}" >/dev/null 2>&1 + if verify "${threshold}" "${group}" "${method}"; then + git --no-pager diff "${diff_ref}" "${curr_ref}" + else + echo "Verification of specifed diff ref failed: ${ref}" + fi + set +x +} + +verify(){ + [ $# -eq 3 ] || die "Usage: verify " + local -r threshold=${1} + local -r group=${2} + local -r method=${3} + if [ -z "$method" ] || [ "$method" == "git" ]; then + if [ "$method" == "git" ]; then + command -v git >/dev/null 2>&1 \ + || die "Error: method 'git' specified and git is not installed" + fi + if command -v git >/dev/null 2>&1 \ + && ( [ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1 ); + then + verify_git "${threshold}" "${group}" || return 1 + fi + fi + + if [ -z "$method" ] || [ "$method" == "detached" ]; then + ( [ -d ".${PROGRAM}" ] && ls ."${PROGRAM}"/*.asc >/dev/null 2>&1 ) || { + echo "Error: No signatures"; + return 1; + } + cmd_manifest || return 1 + verify_detached "${threshold}" "${group}" ."${PROGRAM}"/manifest.txt \ + || return 1 + fi +} ## Public Commands @@ -306,32 +385,26 @@ cmd_manifest() { } cmd_verify() { - local opts threshold=1 group="" method="" - opts="$(getopt -o t:g:m: -l threshold:,group:,method: -n "$PROGRAM" -- "$@")" + local opts threshold=1 group="" method="" diff="" + local -r args="$@" + opts="$(getopt -o t:g:m:d: -l threshold:,group:,method:,diff: -n "$PROGRAM" -- "$@")" eval set -- "$opts" while true; do case $1 in -t|--threshold) threshold="$2"; shift 2 ;; -g|--group) group="$2"; shift 2 ;; -m|--method) method="$2"; shift 2 ;; + -d|--diff) diff="$2"; shift 2 ;; --) shift; break ;; esac done - if [ -z "$method" ] || [ "$method" == "git" ]; then - if [ "$method" == "git" ]; then - command -v git >/dev/null 2>&1 \ - || die "Error: method 'git' specified and git is not installed" - fi - command -v git >/dev/null 2>&1 \ - && ( [ -d .git ] || git rev-parse --git-dir > /dev/null 2>&1 ) \ - && verify_git "${threshold}" "${group}" - fi - - if [ -z "$method" ] || [ "$method" == "detached" ]; then - ( [ -d ".${PROGRAM}" ] && ls ."${PROGRAM}"/*.asc >/dev/null 2>&1 ) \ - || die "Error: No signatures" - cmd_manifest - verify_detached "${threshold}" "${group}" ."${PROGRAM}"/manifest.txt + if verify "$threshold" "$group" "$method"; then + return 0 + elif [ ! -z "$diff" ]; then + echo "Verification failed." + echo "Attempting verified diff against git ref ${diff} ..." + verify_git_diff "$diff" "$threshold" "$group" "$method" fi + return 1 } cmd_fetch() { @@ -405,8 +478,8 @@ cmd_usage() { Usage: $PROGRAM add Add signature to manifest for this directory - $PROGRAM verify [-g,--group=] [-t,--threshold=] [-m,--method= ] - Verify m-of-n signatures by given group are present for directory + $PROGRAM verify [-g,--group=] [-t,--threshold=] [-m,--method= ] [d,--diff=] + Verify m-of-n signatures by given group are present for directory. $PROGRAM fetch [-g,--group=] Fetch key by fingerprint. Optionally add to group. $PROGRAM manifest diff --git a/test/test.bats b/test/test.bats index d3139e5..23c788c 100644 --- a/test/test.bats +++ b/test/test.bats @@ -197,3 +197,29 @@ load test_helper run sig verify --method detached --threshold 2 --group maintainers [ "$status" -eq 1 ] } + +@test "Verify diff shows changes between feature branch and verified master" { + git init + + set_identity "user1" + echo "test string" > testfile + sig add + git add . + git commit -m "User 1 Commit" + + set_identity "user2" + sig add + git add . + git commit -m "User 2 Commit" + + set_identity "user1" + git checkout -b feature_branch + echo "updated test string" > somefile1 + sig add + git add . + git commit -m "User 1 Update Commit" + + run sig verify --diff master --method detached --threshold 2 + [ "$status" -eq 1 ] + echo "${output}" | grep "updated test string" +}