verify/diff against historical refs
This commit is contained in:
parent
9a33b30443
commit
f2fa9cf8fc
135
sig
135
sig
|
@ -114,23 +114,6 @@ check_tools(){
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
### Get files that will be added to the manifest for signing
|
|
||||||
### Use git if available, else fall back to find
|
|
||||||
get_files(){
|
|
||||||
if [ -d '.git' ] && command -v git >/dev/null; then
|
|
||||||
git ls-files \
|
|
||||||
--cached \
|
|
||||||
--others \
|
|
||||||
--exclude-standard \
|
|
||||||
| grep -v ".${PROGRAM}"
|
|
||||||
else
|
|
||||||
find . \
|
|
||||||
-type f \
|
|
||||||
-not -path "./.git/*" \
|
|
||||||
-not -path "./.${PROGRAM}/*"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
### Get primary UID for a given fingerprint
|
### Get primary UID for a given fingerprint
|
||||||
get_uid(){
|
get_uid(){
|
||||||
local -r fp="${1?}"
|
local -r fp="${1?}"
|
||||||
|
@ -226,12 +209,20 @@ group_check_fp(){
|
||||||
}
|
}
|
||||||
|
|
||||||
tree_hash() {
|
tree_hash() {
|
||||||
|
local -r ref="${1:-HEAD}"
|
||||||
|
local -r target=$(git rev-parse ${ref})
|
||||||
|
local -r current=$(git rev-parse HEAD)
|
||||||
|
[ "$target" == "$current" ] || git checkout ${target} >/dev/null 2>&1
|
||||||
mkdir -p ".${PROGRAM}"
|
mkdir -p ".${PROGRAM}"
|
||||||
printf "%s" "$(get_files | xargs openssl sha256 -r)" \
|
printf "%s" "$( \
|
||||||
|
find . -type f -not -path "./.git/*" \
|
||||||
|
| 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 \
|
||||||
| openssl sha256 -r \
|
| openssl sha256 -r \
|
||||||
| sed -e 's/ .*//g'
|
| sed -e 's/ .*//g'
|
||||||
|
[ "$target" == "$current" ] || git checkout ${current} >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
sig_generate(){
|
sig_generate(){
|
||||||
|
@ -253,6 +244,7 @@ sig_generate(){
|
||||||
|
|
||||||
parse_gpg_status() {
|
parse_gpg_status() {
|
||||||
local -r gpg_status="$1"
|
local -r gpg_status="$1"
|
||||||
|
local -r error="$2"
|
||||||
while read -r values; do
|
while read -r values; do
|
||||||
local key array sip_fp sig_date sig_status sig_author sig_body
|
local key array sip_fp sig_date sig_status sig_author sig_body
|
||||||
IFS=" " read -r -a array <<< "$values"
|
IFS=" " read -r -a array <<< "$values"
|
||||||
|
@ -282,13 +274,15 @@ parse_gpg_status() {
|
||||||
esac
|
esac
|
||||||
done <<< "$gpg_status"
|
done <<< "$gpg_status"
|
||||||
sig_fp=$(get_primary_fp $sig_fp)
|
sig_fp=$(get_primary_fp $sig_fp)
|
||||||
sig_body="pgp:$sig_fp:$sig_status:$sig_trust:$sig_date:$sig_author"
|
sig_body="pgp:$sig_fp:$sig_status:$sig_trust:$sig_date:$sig_author:$error"
|
||||||
printf "$sig_body"
|
printf "$sig_body"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_git_note(){
|
verify_git_note(){
|
||||||
local -r line="${1}"
|
local -r line="${1}"
|
||||||
|
local -r ref="${2:-HEAD}"
|
||||||
|
local -r commit=$(git rev-parse ${ref})
|
||||||
IFS=':' line_parts=($line)
|
IFS=':' line_parts=($line)
|
||||||
local -r identifier=${line_parts[0]}
|
local -r identifier=${line_parts[0]}
|
||||||
local -r version=${line_parts[1]}
|
local -r version=${line_parts[1]}
|
||||||
|
@ -298,38 +292,43 @@ verify_git_note(){
|
||||||
local -r sig_type=${line_parts[5]}
|
local -r sig_type=${line_parts[5]}
|
||||||
local -r sig=${line_parts[6]}
|
local -r sig=${line_parts[6]}
|
||||||
local -r body="sig:$version:$vcs_hash:$tree_hash:$review_hash:$sig_type"
|
local -r body="sig:$version:$vcs_hash:$tree_hash:$review_hash:$sig_type"
|
||||||
local sig_status="unknown"
|
local error="" commit_tree_hash
|
||||||
[[ "$identifier" == "sig" \
|
[[ "$identifier" == "sig" \
|
||||||
&& "$version" == "v0" \
|
&& "$version" == "v0" \
|
||||||
&& "$sig_type" == "pgp" \
|
&& "$sig_type" == "pgp" \
|
||||||
]] || {
|
]] || {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
[[ "$tree_hash" == "$(tree_hash)" ]] || {
|
|
||||||
echo "Actual tree hash differs from signature.";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
gpg_sig_raw="$(
|
gpg_sig_raw="$(
|
||||||
gpg --verify --status-fd=1 \
|
gpg --verify --status-fd=1 \
|
||||||
<(printf '%s' "$sig" | openssl base64 -d -A) \
|
<(printf '%s' "$sig" | openssl base64 -d -A) \
|
||||||
<(printf '%s' "$body") 2>/dev/null \
|
<(printf '%s' "$body") 2>/dev/null \
|
||||||
)"
|
)"
|
||||||
parse_gpg_status "$gpg_sig_raw"
|
[[ "$vcs_hash" == "$commit" ]] || {
|
||||||
|
error="COMMIT_NOMATCH"
|
||||||
|
}
|
||||||
|
commit_tree_hash="$(tree_hash ${commit})"
|
||||||
|
[[ "$tree_hash" == "$commit_tree_hash" ]] || {
|
||||||
|
error="TREEHASH_NOMATCH;$commit;$tree_hash;$commit_tree_hash";
|
||||||
|
}
|
||||||
|
parse_gpg_status "$gpg_sig_raw" "$error"
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_git_notes(){
|
verify_git_notes(){
|
||||||
local -r commit=$(git rev-parse --short HEAD)
|
local -r ref="${1:-HEAD}"
|
||||||
|
local -r commit=$(git rev-parse ${ref})
|
||||||
local code=1
|
local code=1
|
||||||
while IFS='' read -r line; do
|
while IFS='' read -r line; do
|
||||||
printf "$(verify_git_note "$line")\n"
|
printf "$(verify_git_note "$line" "$ref")\n"
|
||||||
code=0
|
code=0
|
||||||
done < <(git notes --ref signatures show "$commit" 2>&1 | grep "^sig:")
|
done < <(git notes --ref signatures show "$commit" 2>&1 | grep "^sig:")
|
||||||
return $code
|
return $code
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_git_commit(){
|
verify_git_commit(){
|
||||||
|
local -r ref="${1:-HEAD}"
|
||||||
local gpg_sig_raw
|
local gpg_sig_raw
|
||||||
gpg_sig_raw=$(git verify-commit HEAD --raw 2>&1)
|
gpg_sig_raw=$(git verify-commit ${ref} --raw 2>&1)
|
||||||
parse_gpg_status "$gpg_sig_raw"
|
parse_gpg_status "$gpg_sig_raw"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,18 +347,19 @@ verify_git_tags(){
|
||||||
### Verify head commit is signed
|
### Verify head commit is signed
|
||||||
### Optionally verify total unique commit/tag/note signatures meet a threshold
|
### Optionally verify total unique commit/tag/note signatures meet a threshold
|
||||||
### Optionally verify all signatures belong to keys in gpg alias group
|
### Optionally verify all signatures belong to keys in gpg alias group
|
||||||
verify_git(){
|
verify(){
|
||||||
[ $# -eq 2 ] || die "Usage: verify_git <threshold> <group>"
|
[ $# -eq 3 ] || die "Usage: verify <threshold> <group> <ref>"
|
||||||
local -r threshold="${1}"
|
local -r threshold="${1}"
|
||||||
local -r group="${2}"
|
local -r group="${2}"
|
||||||
|
local -r ref=${3:-HEAD}
|
||||||
local sig_count=0 seen_fps="" fp="" tag_fps="" note_fps=""
|
local sig_count=0 seen_fps="" fp="" tag_fps="" note_fps=""
|
||||||
|
[ -d .git ] \
|
||||||
|
|| die "Error: This folder is not a git repository"
|
||||||
if [[ $(git diff --stat) != '' ]]; then
|
if [[ $(git diff --stat) != '' ]]; then
|
||||||
die "Error: git tree is dirty"
|
die "Error: git tree is dirty"
|
||||||
fi
|
fi
|
||||||
git verify-commit HEAD >/dev/null 2>&1 \
|
|
||||||
|| die "Error: HEAD commit is not signed"
|
|
||||||
|
|
||||||
commit_sig=$(verify_git_commit)
|
commit_sig=$(verify_git_commit "$ref")
|
||||||
if [ -n "$commit_sig" ]; then
|
if [ -n "$commit_sig" ]; then
|
||||||
IFS=':' read -r -a sig <<< "$commit_sig"
|
IFS=':' read -r -a sig <<< "$commit_sig"
|
||||||
fp="${sig[1]}"
|
fp="${sig[1]}"
|
||||||
|
@ -368,7 +368,7 @@ verify_git(){
|
||||||
seen_fps="${fp}"
|
seen_fps="${fp}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tag_sigs=$(verify_git_tags)
|
tag_sigs=$(verify_git_tags "$ref")
|
||||||
[[ $? == 0 ]] && while IFS= read -r line; do
|
[[ $? == 0 ]] && while IFS= read -r line; do
|
||||||
IFS=':' read -r -a sig <<< "$line"
|
IFS=':' read -r -a sig <<< "$line"
|
||||||
fp="${sig[1]}"
|
fp="${sig[1]}"
|
||||||
|
@ -379,12 +379,17 @@ verify_git(){
|
||||||
fi
|
fi
|
||||||
done <<< "$tag_sigs"
|
done <<< "$tag_sigs"
|
||||||
|
|
||||||
note_sigs=$(verify_git_notes)
|
note_sigs=$(verify_git_notes "$ref")
|
||||||
[[ $? == 0 ]] && while IFS= read -r line; do
|
[[ $? == 0 ]] && while IFS= read -r line; do
|
||||||
IFS=':' read -r -a sig <<< "$line"
|
IFS=':' read -r -a sig <<< "$line"
|
||||||
fp="${sig[1]}"
|
fp="${sig[1]}"
|
||||||
uid="${sig[5]}"
|
uid="${sig[5]}"
|
||||||
echo "Verified signed git note by \"${uid}\""
|
error="${sig[6]}"
|
||||||
|
[ "$error" == "" ] || {
|
||||||
|
echo "Error: $error";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
echo "Verified signed git note commit by \"${uid}\""
|
||||||
if [[ "${seen_fps}" != *"${fp}"* ]]; then
|
if [[ "${seen_fps}" != *"${fp}"* ]]; then
|
||||||
seen_fps+=" ${fp}"
|
seen_fps+=" ${fp}"
|
||||||
fi
|
fi
|
||||||
|
@ -417,39 +422,6 @@ get_temp(){
|
||||||
--directory
|
--directory
|
||||||
}
|
}
|
||||||
|
|
||||||
## Verify specified branch and show diff between that and current HEAD
|
|
||||||
verify_git_diff(){
|
|
||||||
[ $# -eq 4 ] \
|
|
||||||
|| die "Usage: verify_git_diff <ref> <threshold> <group> <method>"
|
|
||||||
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)
|
|
||||||
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 specified diff ref failed: ${ref}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
## Verify current folder/repo with specified signing rules
|
|
||||||
verify(){
|
|
||||||
[ $# -eq 3 ] || die "Usage: verify <threshold> <group>"
|
|
||||||
local -r threshold=${1}
|
|
||||||
local -r group=${2}
|
|
||||||
[ -d .git ] \
|
|
||||||
|| die "Error: This folder is not a git repository"
|
|
||||||
verify_git "${threshold}" "${group}" || return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
## Add signed tag pointing at this commit.
|
## Add signed tag pointing at this commit.
|
||||||
## Optionally push to origin.
|
## Optionally push to origin.
|
||||||
sign_tag(){
|
sign_tag(){
|
||||||
|
@ -494,32 +466,24 @@ sign_note() {
|
||||||
|
|
||||||
## Public Commands
|
## Public Commands
|
||||||
|
|
||||||
cmd_manifest() {
|
|
||||||
mkdir -p ".${PROGRAM}"
|
|
||||||
printf "%s" "$(get_files | xargs openssl sha256 -r)" \
|
|
||||||
| sed -e 's/ \*/ /g' -e 's/ \.\// /g' \
|
|
||||||
| LC_ALL=C sort -k2 \
|
|
||||||
> ".${PROGRAM}/manifest.txt"
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd_verify() {
|
cmd_verify() {
|
||||||
local opts threshold=1 group="" method="" diff=""
|
local opts threshold=1 group="" method="" diff=""
|
||||||
opts="$(getopt -o t:g:m:d: -l threshold:,group:,method:,diff: -n "$PROGRAM" -- "$@")"
|
opts="$(getopt -o t:g:m:d:: -l threshold:,group:,ref:,diff:: -n "$PROGRAM" -- "$@")"
|
||||||
eval set -- "$opts"
|
eval set -- "$opts"
|
||||||
while true; do case $1 in
|
while true; do case $1 in
|
||||||
-t|--threshold) threshold="$2"; shift 2 ;;
|
-t|--threshold) threshold="$2"; shift 2 ;;
|
||||||
-g|--group) group="$2"; shift 2 ;;
|
-g|--group) group="$2"; shift 2 ;;
|
||||||
-m|--method) method="$2"; shift 2 ;;
|
-r|--ref) ref="$2"; shift 2 ;;
|
||||||
-d|--diff) diff="$2"; shift 2 ;;
|
-d|--diff) diff="1"; shift 2 ;;
|
||||||
--) shift; break ;;
|
--) shift; break ;;
|
||||||
esac done
|
esac done
|
||||||
|
|
||||||
if verify "$threshold" "$group" "$method"; then
|
local -r head=$(git rev-parse --short HEAD)
|
||||||
|
if verify "$threshold" "$group" "$ref"; then
|
||||||
|
if [ ! -z "$diff" ] && [ ! -z "$ref" ] && [ "${ref}" != "${head}" ]; then
|
||||||
|
git --no-pager diff "${ref}" "${head}"
|
||||||
|
fi
|
||||||
return 0
|
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
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -625,7 +589,6 @@ readonly PROGRAM="${0##*/}"
|
||||||
case "$1" in
|
case "$1" in
|
||||||
verify) shift; cmd_verify "$@" ;;
|
verify) shift; cmd_verify "$@" ;;
|
||||||
add) shift; cmd_add "$@" ;;
|
add) shift; cmd_add "$@" ;;
|
||||||
manifest) shift; cmd_manifest "$@" ;;
|
|
||||||
fetch) shift; cmd_fetch "$@" ;;
|
fetch) shift; cmd_fetch "$@" ;;
|
||||||
push) shift; cmd_push "$@" ;;
|
push) shift; cmd_push "$@" ;;
|
||||||
version|--version) shift; cmd_version "$@" ;;
|
version|--version) shift; cmd_version "$@" ;;
|
||||||
|
|
|
@ -127,6 +127,26 @@ load test_helper
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "Verify succeeds on past commit ref" {
|
||||||
|
git init
|
||||||
|
|
||||||
|
set_identity "user1"
|
||||||
|
echo "test string" > testfile
|
||||||
|
git add .
|
||||||
|
git commit -m "User 1 Commit"
|
||||||
|
|
||||||
|
set_identity "user2"
|
||||||
|
sig add
|
||||||
|
|
||||||
|
set_identity "user1"
|
||||||
|
echo "updated test string" > somefile1
|
||||||
|
git add .
|
||||||
|
git commit -m "User 1 Update Commit"
|
||||||
|
|
||||||
|
run sig verify --threshold 2 --ref HEAD~1
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "Verify diff shows changes between feature branch and verified master" {
|
@test "Verify diff shows changes between feature branch and verified master" {
|
||||||
git init
|
git init
|
||||||
|
|
||||||
|
@ -144,7 +164,7 @@ load test_helper
|
||||||
git add .
|
git add .
|
||||||
git commit -m "User 1 Update Commit"
|
git commit -m "User 1 Update Commit"
|
||||||
|
|
||||||
run sig verify --diff master --threshold 2
|
run sig verify --diff --ref master --threshold 2
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 0 ]
|
||||||
echo "${output}" | grep "updated test string"
|
echo "${output}" | grep "updated test string"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue