aboutsummaryrefslogtreecommitdiff
path: root/contrib/verify-commits/verify-commits.sh
blob: 6415eea4d53fe805367f1cf7ed06d5e7339f55db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/bin/sh
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

DIR=$(dirname "$0")
[ "/${DIR#/}" != "$DIR" ] && DIR=$(dirname "$(pwd)/$0")

echo "Using verify-commits data from ${DIR}"

VERIFIED_ROOT=$(cat "${DIR}/trusted-git-root")
VERIFIED_SHA512_ROOT=$(cat "${DIR}/trusted-sha512-root-commit")
REVSIG_ALLOWED=$(cat "${DIR}/allow-revsig-commits")

HAVE_GNU_SHA512=1
[ ! -x "$(which sha512sum)" ] && HAVE_GNU_SHA512=0

if [ x"$1" = "x" ]; then
	CURRENT_COMMIT="HEAD"
else
	CURRENT_COMMIT="$1"
fi

if [ "${CURRENT_COMMIT#* }" != "$CURRENT_COMMIT" ]; then
	echo "Commit must not contain spaces?" > /dev/stderr
	exit 1
fi

VERIFY_TREE=0
if [ x"$2" = "x--tree-checks" ]; then
	VERIFY_TREE=1
fi

NO_SHA1=1
PREV_COMMIT=""
INITIAL_COMMIT="${CURRENT_COMMIT}"

BRANCH="$(git rev-parse --abbrev-ref HEAD)"

while true; do
	if [ "$CURRENT_COMMIT" = $VERIFIED_ROOT ]; then
		echo "There is a valid path from \"$INITIAL_COMMIT\" to $VERIFIED_ROOT where all commits are signed!"
		exit 0
	fi

	if [ "$CURRENT_COMMIT" = $VERIFIED_SHA512_ROOT ]; then
		if [ "$VERIFY_TREE" = "1" ]; then
			echo "All Tree-SHA512s matched up to $VERIFIED_SHA512_ROOT" > /dev/stderr
		fi
		VERIFY_TREE=0
		NO_SHA1=0
	fi

	if [ "$NO_SHA1" = "1" ]; then
		export BITCOIN_VERIFY_COMMITS_ALLOW_SHA1=0
	else
		export BITCOIN_VERIFY_COMMITS_ALLOW_SHA1=1
	fi

	if [ "${REVSIG_ALLOWED#*$CURRENT_COMMIT}" != "$REVSIG_ALLOWED" ]; then
		export BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG=1
	else
		export BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG=0
	fi

	if ! git -c "gpg.program=${DIR}/gpg.sh" verify-commit "$CURRENT_COMMIT" > /dev/null; then
		if [ "$PREV_COMMIT" != "" ]; then
			echo "No parent of $PREV_COMMIT was signed with a trusted key!" > /dev/stderr
			echo "Parents are:" > /dev/stderr
			PARENTS=$(git show -s --format=format:%P $PREV_COMMIT)
			for PARENT in $PARENTS; do
				git show -s $PARENT > /dev/stderr
			done
		else
			echo "$CURRENT_COMMIT was not signed with a trusted key!" > /dev/stderr
		fi
		exit 1
	fi

	# We always verify the top of the tree
	if [ "$VERIFY_TREE" = 1 -o "$PREV_COMMIT" = "" ]; then
		IFS_CACHE="$IFS"
		IFS='
'
		for LINE in $(git ls-tree --full-tree -r "$CURRENT_COMMIT"); do
			case "$LINE" in
				"12"*)
					echo "Repo contains symlinks" > /dev/stderr
					IFS="$IFS_CACHE"
					exit 1
					;;
			esac
		done
		IFS="$IFS_CACHE"

		FILE_HASHES=""
		for FILE in $(git ls-tree --full-tree -r --name-only "$CURRENT_COMMIT" | LC_ALL=C sort); do
			if [ "$HAVE_GNU_SHA512" = 1 ]; then
				HASH=$(git cat-file blob "$CURRENT_COMMIT":"$FILE" | sha512sum | { read FIRST _; echo $FIRST; } )
			else
				HASH=$(git cat-file blob "$CURRENT_COMMIT":"$FILE" | shasum -a 512 | { read FIRST _; echo $FIRST; } )
			fi
			[ "$FILE_HASHES" != "" ] && FILE_HASHES="$FILE_HASHES"'
'
			FILE_HASHES="$FILE_HASHES$HASH  $FILE"
		done

		if [ "$HAVE_GNU_SHA512" = 1 ]; then
			TREE_HASH="$(echo "$FILE_HASHES" | sha512sum)"
		else
			TREE_HASH="$(echo "$FILE_HASHES" | shasum -a 512)"
		fi
		HASH_MATCHES=0
		MSG="$(git show -s --format=format:%B "$CURRENT_COMMIT" | tail -n1)"

		case "$MSG  -" in
			"Tree-SHA512: $TREE_HASH")
				HASH_MATCHES=1;;
		esac

		if [ "$HASH_MATCHES" = "0" ]; then
			echo "Tree-SHA512 did not match for commit $CURRENT_COMMIT" > /dev/stderr
			exit 1
		fi
	fi

	PARENTS=$(git show -s --format=format:%P "$CURRENT_COMMIT")
	PARENT1=${PARENTS%% *}
	PARENT2=""
	if [ "x$PARENT1" != "x$PARENTS" ]; then
		PARENTX=${PARENTS#* }
		PARENT2=${PARENTX%% *}
		if [ "x$PARENT2" != "x$PARENTX" ]; then
			echo "Commit $CURRENT_COMMIT is an octopus merge" > /dev/stderr
			exit 1
		fi
	fi
	if [ "x$PARENT2" != "x" ]; then
		CURRENT_TREE="$(git show --format="%T" "$CURRENT_COMMIT")"
		git checkout --force --quiet "$PARENT1"
		git merge --no-ff --quiet "$PARENT2" >/dev/null
		RECREATED_TREE="$(git show --format="%T" HEAD)"
		if [ "$CURRENT_TREE" != "$RECREATED_TREE" ]; then
			echo "Merge commit $CURRENT_COMMIT is not clean" > /dev/stderr
			git diff "$CURRENT_COMMIT"
			git checkout --force --quiet "$BRANCH"
			exit 1
		fi
		git checkout --force --quiet "$BRANCH"
	fi
	PREV_COMMIT="$CURRENT_COMMIT"
	CURRENT_COMMIT="$PARENT1"
done