git-remote-gcrypt: A git remote helper for GPG-encrypted remotes
This commit is contained in:
parent
22bdc895bd
commit
149f75e188
2 changed files with 343 additions and 0 deletions
0
README
Normal file
0
README
Normal file
343
git-remote-gcrypt
Executable file
343
git-remote-gcrypt
Executable file
|
@ -0,0 +1,343 @@
|
|||
#!/bin/sh
|
||||
|
||||
# git-remote-gcrypt
|
||||
# Copyright 2013 by Ulrik
|
||||
# License: GPLv2 or any later version, see http://www.gnu.org/licenses/
|
||||
#
|
||||
# Requires GnuPG
|
||||
#
|
||||
# We read git config gcrypt.recipients when creating new repositories
|
||||
|
||||
#set -x
|
||||
set -e
|
||||
LANG=C
|
||||
|
||||
genkey()
|
||||
{
|
||||
gpg --armor --gen-rand 1 128 | tr -d \\n
|
||||
}
|
||||
|
||||
sha1()
|
||||
{
|
||||
gpg --print-md sha1 | tr -d ' ' | tr A-F a-f
|
||||
}
|
||||
|
||||
LOCALDIR="${GIT_DIR:-.git}/remote-gcrypt"
|
||||
DUMMYKEY="00000000000000000000"
|
||||
|
||||
isurl() { test -z ${2%%"$1"://*} ; }
|
||||
|
||||
# Split $1 into $prefix_:$suffix_
|
||||
splitcolon()
|
||||
{
|
||||
prefix_=${1%%:*}
|
||||
suffix_=${1#*:}
|
||||
}
|
||||
|
||||
# Fetch repo $1, file $2
|
||||
GET()
|
||||
{
|
||||
if isurl ssh "$1"
|
||||
then
|
||||
splitcolon ${1#ssh://}
|
||||
(exec 0>&-; ssh "$prefix_" "cat $suffix_/$2")
|
||||
elif isurl sftp "$1"
|
||||
then
|
||||
(exec 0>&-; curl -s -S -k "$1/$2")
|
||||
else
|
||||
cat "$1/$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# Fetch repo $1, file $2 or return encrypted empty message
|
||||
GET_OR_EMPTY() { GET "$@" 2>/dev/null || (printf "" | ENCRYPT) ; }
|
||||
|
||||
# Put repo $1, file $2 or fail
|
||||
PUT()
|
||||
{
|
||||
if isurl ssh "$1"
|
||||
then
|
||||
splitcolon ${1#ssh://}
|
||||
ssh "$prefix_" "cat > $suffix_/$2"
|
||||
elif isurl sftp "$1"
|
||||
then
|
||||
curl -s -S -k --ftp-create-dirs -T - "$1/$2"
|
||||
else
|
||||
cat > "$1/$2"
|
||||
fi
|
||||
}
|
||||
|
||||
# Put directory for repo $1
|
||||
PUTREPO()
|
||||
{
|
||||
if isurl ssh "$1"
|
||||
then
|
||||
splitcolon ${1#ssh://}
|
||||
(exec 0>&- ; ssh "$prefix_" "mkdir -p $suffix_")
|
||||
elif isurl sftp "$1"
|
||||
then
|
||||
:
|
||||
else
|
||||
mkdir -p "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
ENCRYPT()
|
||||
{
|
||||
(printf "%s" "$MASTERKEY" | \
|
||||
gpg --batch --force-mdc --cipher-algo AES \
|
||||
--passphrase-fd 0 --output - -c /dev/fd/3) 3<&0
|
||||
}
|
||||
|
||||
DECRYPT()
|
||||
{
|
||||
(printf "%s" "$MASTERKEY" | \
|
||||
gpg -q --batch --passphrase-fd 0 --output - -d /dev/fd/3) 3<&0
|
||||
}
|
||||
|
||||
tac() { sed '1!G;h;$!d'; }
|
||||
echo_info() { echo "$@" >&2; }
|
||||
|
||||
make_new_repo()
|
||||
{
|
||||
# Security protocol
|
||||
# The MASTERKEY is encrypted to all RECIPIENTS
|
||||
local RECIPIENTS
|
||||
echo_info "Setting up new repository at $URL"
|
||||
RECIPIENTS=$(git config gcrypt.recipients | sed -e 's/\([^ ]\+\)/-R &/g')
|
||||
if [ -z "$RECIPIENTS" ]
|
||||
then
|
||||
echo_info "> You must configure which GnuPG recipients can access the repository."
|
||||
echo_info "> To setup for all your git repositories, use::"
|
||||
echo_info "> git config --global gcrypt.recipients KEYID"
|
||||
exit 1
|
||||
fi
|
||||
PUTREPO "$URL"
|
||||
# Use an ascii key for GnuPG (due to its input limitations)
|
||||
echo_info "Generating new master key"
|
||||
MASTERKEY="$(genkey)"
|
||||
printf "%s" "$MASTERKEY" | gpg -e $RECIPIENTS | PUT "$URL" masterkey
|
||||
}
|
||||
|
||||
get_masterkey()
|
||||
{
|
||||
(GET "$URL" masterkey 2>/dev/null || : ) | \
|
||||
(gpg -q --batch -d || printf "%s" "$DUMMYKEY")
|
||||
}
|
||||
|
||||
do_capabilities()
|
||||
{
|
||||
echo fetch
|
||||
echo push
|
||||
echo
|
||||
}
|
||||
|
||||
do_list()
|
||||
{
|
||||
local OBJID
|
||||
local REFNAME
|
||||
printf "%s\n" "$MANIFESTDATA" | while read LINE
|
||||
do
|
||||
OBJID=${LINE%% *}
|
||||
REFNAME=${LINE##* }
|
||||
echo "$OBJID" "$REFNAME"
|
||||
if [ "$REFNAME" = "refs/heads/master" ]
|
||||
then
|
||||
echo "@refs/heads/master HEAD"
|
||||
fi
|
||||
done
|
||||
|
||||
# end with blank line
|
||||
echo
|
||||
}
|
||||
|
||||
do_fetch()
|
||||
{
|
||||
# Security protocol:
|
||||
# The PACK id is the sha-1 of the encrypted git packfile.
|
||||
# We only download packs mentioned in the encrypted 'packfest',
|
||||
# and check their digest when received.
|
||||
local PNEED
|
||||
local PREMOTE
|
||||
local PBOTH
|
||||
local PHAVE
|
||||
touch "$LOCALDIR/packfest"
|
||||
PREMOTE="$(GET_OR_EMPTY "$URL" packfest | DECRYPT)"
|
||||
if [ -z "$PREMOTE" ]
|
||||
then
|
||||
echo # end with blank line
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TMPPACK_ENCRYPTED="$LOCALDIR/tmp_pack_ENCRYPTED_.$$"
|
||||
trap 'rm -f "$TMPPACK_ENCRYPTED"' EXIT
|
||||
|
||||
# Needed packs is REMOTE - (HAVE & REMOTE)
|
||||
PHAVE="$(cat "$LOCALDIR/packfest")"
|
||||
PBOTH="$(printf "%s\n%s" "$PREMOTE" "$PHAVE" | sort | uniq -d)"
|
||||
PNEED="$(printf "%s\n%s" "$PREMOTE" "$PBOTH" | sort | uniq -u)"
|
||||
|
||||
printf "%s\n" "$PNEED" | while read PACK
|
||||
do
|
||||
RCVID="$(GET "$URL" "$PACK" | tee "$TMPPACK_ENCRYPTED" | sha1)"
|
||||
if [ "$RCVID" != "$PACK" ]
|
||||
then
|
||||
echo_info "Packfile $PACK does not match digest!"
|
||||
exit 1
|
||||
fi
|
||||
cat "$TMPPACK_ENCRYPTED" | DECRYPT | git unpack-objects
|
||||
|
||||
# add to local pack list
|
||||
printf "%s\n" "$PACK" >> "$LOCALDIR/packfest"
|
||||
done
|
||||
|
||||
rm -f "$TMPPACK_ENCRYPTED"
|
||||
trap EXIT
|
||||
echo # end with blank line
|
||||
}
|
||||
|
||||
# do_push PUSHARGS (multiple lines)
|
||||
do_push()
|
||||
{
|
||||
# each line is (with optional `+` and src)
|
||||
# +src:dst
|
||||
local REMOTEHAS
|
||||
local REMOTEWANT
|
||||
local PACKFEST
|
||||
local prefix_
|
||||
local suffix_
|
||||
|
||||
if [ "$MASTERKEY" = "$DUMMYKEY" ]
|
||||
then
|
||||
make_new_repo
|
||||
fi
|
||||
|
||||
trap 'rm -f "$TMPMANIFEST" "$TMPPACK_ENCRYPTED" "$TMPOBJLIST"' EXIT
|
||||
TMPMANIFEST="$LOCALDIR/tmp_new_manifest_.$$"
|
||||
touch "$TMPMANIFEST"
|
||||
if [ ! -z "$MANIFESTDATA" ]
|
||||
then
|
||||
printf "%s\n" "$MANIFESTDATA" > "$TMPMANIFEST"
|
||||
REMOTEHAS="$(printf "%s" "$MANIFESTDATA" | \
|
||||
cut -f1 -d' ' | sed -e s/^/^/ | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
REMOTEWANT="$(printf "%s\n" "$1" | while read LINE
|
||||
do
|
||||
# +src:dst -- remove leading + then split at :
|
||||
splitcolon ${LINE#+}
|
||||
if [ ! -z "$prefix_" ]
|
||||
then
|
||||
printf "%s " "$prefix_"
|
||||
printf "%s %s\n" $(git rev-parse "$prefix_") "$suffix_" >> "$TMPMANIFEST"
|
||||
# else delete
|
||||
fi
|
||||
done)"
|
||||
|
||||
# POSIX compat issue: sort -s (stable), but supported in bsd and gnu
|
||||
MANIFESTDATA="$(cat "$TMPMANIFEST" | sort -k2 -s | tac | uniq -s40)"
|
||||
|
||||
TMPPACK_ENCRYPTED="$LOCALDIR/tmp_pack_ENCRYPTED_.$$"
|
||||
TMPOBJLIST="$LOCALDIR/tmp_packrevlist.$$"
|
||||
git rev-list --objects $REMOTEHAS $REMOTEWANT -- | \
|
||||
tee "$TMPOBJLIST" | \
|
||||
git pack-objects --stdout | ENCRYPT > "$TMPPACK_ENCRYPTED"
|
||||
# Only send pack if we have any objects to send
|
||||
if [ -s "$TMPOBJLIST" ]
|
||||
then
|
||||
PACKID=$(cat "$TMPPACK_ENCRYPTED" | sha1)
|
||||
PACKFEST="$(GET_OR_EMPTY "$URL" packfest | DECRYPT)"
|
||||
if [ -z "$PACKFEST" ]
|
||||
then
|
||||
PACKFEST="$(printf "%s\n" "$PACKID")"
|
||||
else
|
||||
PACKFEST="$(printf "%s\n%s\n" "$PACKFEST" "$PACKID")"
|
||||
fi
|
||||
|
||||
cat "$TMPPACK_ENCRYPTED" | PUT "$URL" "$PACKID"
|
||||
printf "%s\n" "$PACKFEST" | ENCRYPT | PUT "$URL" "packfest"
|
||||
fi
|
||||
|
||||
printf "%s\n" "$MANIFESTDATA" | ENCRYPT | PUT "$URL" "manifest"
|
||||
|
||||
# ok all updates (not deletes)
|
||||
printf "%s\n" "$1" | while read LINE
|
||||
do
|
||||
# +src:dst -- remove leading + then split at :
|
||||
splitcolon ${LINE#+}
|
||||
if [ -z "$prefix_" ]
|
||||
then
|
||||
echo "error $suffix_ delete not supported yet"
|
||||
else
|
||||
echo "ok $suffix_"
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f "$TMPPACK_ENCRYPTED"
|
||||
rm -f "$TMPMANIFEST"
|
||||
rm -f "$TMPOBJLIST"
|
||||
trap EXIT
|
||||
echo
|
||||
}
|
||||
|
||||
# Main program, check $URL is supported
|
||||
NAME=$1
|
||||
URL=$2
|
||||
( isurl ssh "$URL" || isurl sftp "$URL" || test -z ${URL##/*} ) || \
|
||||
{ echo_info "Supported URLs: Absolute path, sftp://, ssh://" ; exit 1 ; }
|
||||
|
||||
mkdir -p "$LOCALDIR"
|
||||
MASTERKEY="$(get_masterkey)"
|
||||
MANIFESTDATA="$(GET_OR_EMPTY "$URL" manifest | DECRYPT)"
|
||||
|
||||
while read INPUT
|
||||
do
|
||||
#echo_info "Got: $INPUT"
|
||||
case "$INPUT" in
|
||||
capabilities)
|
||||
do_capabilities
|
||||
;;
|
||||
list|list\ for-push)
|
||||
do_list
|
||||
;;
|
||||
fetch\ *)
|
||||
FETCH_ARGS="${INPUT##fetch }"
|
||||
while read INPUTX
|
||||
do
|
||||
case "$INPUTX" in
|
||||
fetch*)
|
||||
FETCH_ARGS= #ignored
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
do_fetch "$FETCH_ARGS"
|
||||
;;
|
||||
push\ *)
|
||||
PUSH_ARGS="${INPUT##push }"
|
||||
while read INPUTX
|
||||
do
|
||||
#echo_info "Got: (for push) $INPUTX"
|
||||
case "$INPUTX" in
|
||||
push\ *)
|
||||
PUSH_ARGS="$(printf "%s\n%s" "$PUSH_ARGS" "${INPUTX#push }")"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
do_push "$PUSH_ARGS"
|
||||
;;
|
||||
?*)
|
||||
echo_info "Unknown input!"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
#echo_info "Blank line, we are done"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
Loading…
Reference in a new issue