group support for git sigs

This commit is contained in:
Lance Vick 2020-11-16 16:22:24 -08:00
parent b53757b062
commit a56a9c477e
Signed by: lrvick
GPG Key ID: 8E47A1EC35A1551D
4 changed files with 81 additions and 52 deletions

View File

@ -1,16 +1,16 @@
-----BEGIN PGP SIGNATURE----- -----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEZ1U/vaRrtxq9LgsLjkeh7DWhVR0FAl+ydC8ACgkQjkeh7DWh iQIzBAABCgAdFiEEZ1U/vaRrtxq9LgsLjkeh7DWhVR0FAl+zF58ACgkQjkeh7DWh
VR09Lg/9F+xu75alg8aXBSw2JJXf90OrZukVi54FoIMoziYbQLPazu8TqvcS9x4M VR2DbQ//fLBe5OUcrT7O4TWguYPOYmQJSNM7NWG2tNS4roiJFaJUPhx0djnEIhqI
5Z9Aw1GJlkNXd02zqLNO41twLAmbfBUjm+FUa3oB6MkyIijjgLk913XjUmy+ZHw1 4Ui8FJfXyowHYDyxbtzL165bl2LcjUVlp2+xyTZxZwqh/TGVSqSy0tioepiWiTO3
kvPihdiNFefzQsWAmw+Qvt+S9sXLQ4h/CIc86/Skb61ivoY5V1VR2zIZfUIg4na1 f/aVki7j7TkGwi/fPb1t7WpwbGlMxgXDGFsYYZ8+MWi7XQEg72bl+dOHUy8EsDBd
rO3mSWCzO0H7EOCb+ahB716Oal4Byw7AsbQjSIt25Nlqq4l5j7AQnDKHo/tiLNS8 jZKfWuc6NcpUwip814WAQBTWQbRrNdyTPTn/jJ974Ba75trEY9RMbMbaDw80Aw98
RNeU5mIjQ3kPOWtSXGYYuxS0VjVap33ZAGsHx6g2NrrO3wbb2BoGsBAHGsFlUetd KoRSo6zDu/xT9UBoxWfRO+aMahtYqKNO+F8PmXFxXGI0Gi3iwciv82H5h3KH52Cx
wq8pbLkA+9HXTvIwR+2llKp0i+2uhPWiD+/SNKVWIqrRWr6ySWDjXHjut5cP74zo LHAeUApApI5xD0A7s/UfOei48ed34XiSymbJG6ppIwaEsXNIgbe3qhdx4YvGQ9X0
lEE4qBweT4PuW9YmEIvQaKHUKeTvP/sWYinKizVtM3jmbcFfTAWqdMeCc+V0kZhb euvRGxRGbOZjCPalqQfu4Z7CWywTPdX2q/usyPe7u+D8SzD0ICdhBk0A86qi4J84
p56s9VyZKHG7FGMueXcRsFV8QnESOFZ6BlybRFUFJt6pgFcGUa9QVLHDudn+hsJI /1cWHmtotssN9tNNLGGoGKmDkxawqElQWeHTlNvxzFzW5CI9KyYPD5cxaFtr+jsY
dhiCs5LKxFYoAKEXzJTafMqnnuwRGVrDCbBn6tL9wzBn0MerYD4YEmegglnEtNpX yc2Av6PDnKOLH3HFUo03eoeq9p7ov5NGQswDFxdlTEDcbpaPZDcYiV+Z5zJPMJOs
nbo/+15VXsiU90hoXJGDsVOdCEJRyrANL98NhAci3+2IKQQQY6VgFX0TJqKrJFFc ve2otw9WcximzSs9mryNgLEFSlRTG31oNzLI5E92oHS3pkYRNgW0Wr1SpzetRk+6
qNNV+5VQbcJcceSwZUg/dUCYkTmhvW6Vn9vB7R8AX0r+KT4ByWM= JhCp7f3C2dwUx3/TxeNX/F5j/vQvYrbS0Km83a7CzgEIvfrJEYs=
=R1GH =8zXb
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----

View File

@ -1,3 +1,3 @@
64263feac7b00952e9ec3b6c1fd11316faa58ff673c6bd085fac9f6f8d8389f6 .gitignore 64263feac7b00952e9ec3b6c1fd11316faa58ff673c6bd085fac9f6f8d8389f6 .gitignore
3725c292d31a893395e3316046a2ac9264410f62e0a3bf0ae774cff6234d8533 README.md c5649b242a88aec7617c627b75d3d6c6057123fdd9f1ebd8fee1d7001a827da0 README.md
b470a31ed13fb1177a0ffae1872f3e24f9aa8292090f4d838ad2a4bc639f8404 sig bbe6f757d0f17cd0eab5b4cafbd7351e2cd403a7386ac47a2a894115b6f29683 sig

View File

@ -124,3 +124,37 @@ sig verify --threshold 2 --group myteam --method git
``` ```
sig add sig add
``` ```
## Frequently Asked Questions
### Why Bash?
Because it is easy to quickly verify at any time, has wide OS compatibility andthe majority of the needed operations are calling other programs already on
your system like gpg and openssl.
If this were in another language it would be harder to audit on the fly, would
require the user to have a specific language toolchain installed, and it would
still mostly just be a bunch of shell executions to call system binaries
anyway.
### Why PGP?
In spite of many popular claims to the contrary, PGP is still the most well
supported protocol for distribution, verification, and signing for keys held
by individual humans. It is also the only protocoal with wide HSM support
allowing you to keep keys out of system memory and requier physical approval
for each operation. E.G a trezor, ledger, or yubikey.
Popular alternatives like signify or straight openssl have poor support for
these workflows.
Admittedly the GnuPG codebase itself is a buggy dated mess, but PGP as a spec
is still Pretty Good for many use cases. A recent modern rewrite by a number
of former GnuPG team members is near complete and set to give PGP a long and
stable future.
See: https://sequoia-pgp.org/
The only promising alternative to GnuPG for software signing that has hsm
support and the very attractive feature of expiring signatures is [The Update Framework](https://theupdateframework.io) which may be supported as an alternate
method in the future if m-of-n multisig is ever implemented.

69
sig
View File

@ -36,7 +36,7 @@ die_pkg() {
*) die "Error: Your operating system is not supported" ;; *) die "Error: Your operating system is not supported" ;;
esac esac
echo "Error: ${package} ${version}+ does not appear to be installed." >&2 echo "Error: ${package} ${version}+ does not appear to be installed." >&2
[ ! -z "$install_cmd" ] && printf "Try: \`${install_cmd}\`" >&2 [ ! -z "$install_cmd" ] && echo "Try: \`${install_cmd}\`" >&2
exit 1 exit 1
} }
@ -60,9 +60,8 @@ check_version(){
### Check if required binaries are installed at appropriate versions ### Check if required binaries are installed at appropriate versions
check_tools(){ check_tools(){
if [ -z "${BASH_VERSINFO}" ] \ if [ -z "${BASH_VERSINFO[0]}" ] \
|| [ -z "${BASH_VERSINFO[0]}" ] \ || [ "${BASH_VERSINFO[0]}" -lt "${MIN_BASH_VERSION}" ]; then
|| [ ${BASH_VERSINFO[0]} -lt ${MIN_BASH_VERSION} ]; then
die_pkg "bash" "${MIN_BASH_VERSION}" die_pkg "bash" "${MIN_BASH_VERSION}"
fi fi
for cmd in "$@"; do for cmd in "$@"; do
@ -84,19 +83,6 @@ check_tools(){
done done
} }
### Handle different implementations of mktemp across platforms
get_temp(){
echo "$(
mktemp \
--quiet \
--directory \
-t "$(basename "$0").XXXXXX" 2>/dev/null
|| mktemp \
--quiet \
--directory
)"
}
### Get files that will be added to the manifest for signing ### Get files that will be added to the manifest for signing
### Use git if available, else fall back to find ### Use git if available, else fall back to find
get_files(){ get_files(){
@ -120,6 +106,13 @@ get_signer(){
| head -n1 | head -n1
} }
get_group_config(){
local group=${1?}
gpg --with-colons --list-config group \
| grep -i "^cfg:group:${group}:" \
|| die "Error: group \"${group}\" not found in ~/.gnupg/gpg.conf"
}
### Verify a file has 0-N unique valid detached signatures ### Verify a file has 0-N unique valid detached signatures
### Optionally verify all signatures belong to keys in gpg alias group ### Optionally verify all signatures belong to keys in gpg alias group
verify_detached() { verify_detached() {
@ -133,13 +126,6 @@ verify_detached() {
local fingerprint local fingerprint
local signer local signer
if [ ! -z "$group" ]; then
group_config="$( \
gpg --with-colons --list-config group \
| grep -i "^cfg:group:${group}:" \
)" || die "Error: group \"${group}\" not found in ~/.gnupg/gpg.conf"
fi
for sig_filename in "${filename%.*}".*.asc; do for sig_filename in "${filename%.*}".*.asc; do
gpg --verify "${sig_filename}" "${filename}" >/dev/null 2>&1 || { gpg --verify "${sig_filename}" "${filename}" >/dev/null 2>&1 || {
echo "Invalid signature: ${sig_filename}"; echo "Invalid signature: ${sig_filename}";
@ -155,9 +141,11 @@ verify_detached() {
[[ "${seen_fingerprints}" == *"${fingerprint}"* ]] \ [[ "${seen_fingerprints}" == *"${fingerprint}"* ]] \
&& die "Duplicate signature: ${sig_filename}"; && die "Duplicate signature: ${sig_filename}";
[ ! -z "$group_config" ] \ if [ ! -z "$group" ]; then
&& [[ "${group_config}" != *"${fingerprint}"* ]] \ group_config=$(get_group_config "${group}")
[[ "${group_config}" != *"${fingerprint}"* ]] \
&& die "Signature not in group \"${group}\": ${sig_filename}"; && die "Signature not in group \"${group}\": ${sig_filename}";
fi
echo "Verified detached signature by \"${signer}\"" echo "Verified detached signature by \"${signer}\""
@ -175,10 +163,10 @@ verify_git(){
[ $# -eq 2 ] || die "Usage: verify_git <threshold> <group>" [ $# -eq 2 ] || die "Usage: verify_git <threshold> <group>"
local threshold="${1}" local threshold="${1}"
local group="${2}" local group="${2}"
local sig_count=0 local group_config=""
local seen_fingerprints="" local seen_fingerprints=""
local sig_count=0
local depth=0 local depth=0
#TODO: implement group validation
while [[ $depth != "$(git rev-list --count HEAD)" ]]; do while [[ $depth != "$(git rev-list --count HEAD)" ]]; do
ref=HEAD~${depth} ref=HEAD~${depth}
@ -189,6 +177,12 @@ verify_git(){
git verify-commit HEAD~${depth} >/dev/null 2>&1\ git verify-commit HEAD~${depth} >/dev/null 2>&1\
|| die "Unsigned commit: ${commit}" || die "Unsigned commit: ${commit}"
if [ ! -z "$group" ]; then
group_config=$(get_group_config "${group}")
[[ "${group_config}" != *"${fingerprint}"* ]] \
&& die "Git signing key not in group \"${group}\": ${fingerprint}";
fi
[[ "${seen_fingerprints}" != *"${fingerprint}"* ]] \ [[ "${seen_fingerprints}" != *"${fingerprint}"* ]] \
&& seen_fingerprints="${seen_fingerprints} ${fingerprint}" \ && seen_fingerprints="${seen_fingerprints} ${fingerprint}" \
&& ((sig_count=sig_count+1)) \ && ((sig_count=sig_count+1)) \
@ -208,7 +202,7 @@ verify_git(){
cmd_manifest() { cmd_manifest() {
mkdir -p ".${PROGRAM}" mkdir -p ".${PROGRAM}"
printf "$(get_files | xargs openssl sha256 -r)" \ printf "%s" "$(get_files | xargs openssl sha256 -r)" \
| sed -e 's/ \*/ /g' -e 's/ \.\// /g' \ | sed -e 's/ \*/ /g' -e 's/ \.\// /g' \
| LC_ALL=C sort -k2 \ | LC_ALL=C sort -k2 \
> ".${PROGRAM}/manifest.txt" > ".${PROGRAM}/manifest.txt"
@ -225,7 +219,7 @@ cmd_verify() {
--) shift; break ;; --) shift; break ;;
esac done esac done
if ( [ -z "$method" ] || [ "$method" == "git" ] ); then if [ -z "$method" ] || [ "$method" == "git" ]; then
if [ "$method" == "git" ]; then if [ "$method" == "git" ]; then
command -v git >/dev/null 2>&1 \ command -v git >/dev/null 2>&1 \
|| die "Error: method 'git' specified and git is not installed" || die "Error: method 'git' specified and git is not installed"
@ -235,23 +229,24 @@ cmd_verify() {
&& verify_git "${threshold}" "${group}" && verify_git "${threshold}" "${group}"
fi fi
if ( [ -z "$method" ] || [ "$method" == "detached" ] ); then if [ -z "$method" ] || [ "$method" == "detached" ]; then
( [ -d ".${PROGRAM}" ] && ls .${PROGRAM}/*.asc >/dev/null 2>&1 ) \ ( [ -d ".${PROGRAM}" ] && ls ."${PROGRAM}"/*.asc >/dev/null 2>&1 ) \
|| die "Error: No signatures" || die "Error: No signatures"
cmd_manifest cmd_manifest
verify_detached "${threshold}" "${group}" .${PROGRAM}/manifest.txt verify_detached "${threshold}" "${group}" ."${PROGRAM}"/manifest.txt
fi fi
} }
cmd_add(){ cmd_add(){
local fingerprint
cmd_manifest cmd_manifest
gpg --armor --detach-sig .${PROGRAM}/manifest.txt gpg --armor --detach-sig ."${PROGRAM}"/manifest.txt >/dev/null 2>&1
local fingerprint=$( \ fingerprint=$( \
gpg --list-packets .${PROGRAM}/manifest.txt.asc \ gpg --list-packets ."${PROGRAM}"/manifest.txt.asc \
| grep "issuer key ID" \ | grep "issuer key ID" \
| sed 's/.*\([A-Z0-9]\{16\}\).*/\1/g' \ | sed 's/.*\([A-Z0-9]\{16\}\).*/\1/g' \
) )
mv .${PROGRAM}/manifest.{"txt.asc","${fingerprint}.asc"} mv ."${PROGRAM}"/manifest.{"txt.asc","${fingerprint}.asc"}
} }
cmd_version() { cmd_version() {