aboutsummaryrefslogtreecommitdiff
blob: 5ada4b54715475a9bb1cc925c2cf6a7948381cc4 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/bin/bash
# gentoo-infra: infra/githooks.git:update-02-gpg

# --- Command line
refname=${1}
oldrev=${2}
newrev=${3}

# --- Safety check
if [ -z "${GIT_DIR}" ]; then
	echo "Don't run this script from the command line." >&2
	echo " (if you want, you could supply GIT_DIR then run" >&2
	echo "  ${0} <ref> <oldrev> <newrev>)" >&2
	exit 1
fi

if [ -z "${refname}" ] || [ -z "${oldrev}" ] || [ -z "${newrev}" ]; then
	echo "usage: ${0} <ref> <oldrev> <newrev>" >&2
	exit 1
fi

# branch names or 'all', or 'all-refs' for all refs
SIGNED_BRANCHES=$(git config --get gentoo.signed-branches)
: "${SIGNED_BRANCHES:=master}"
VERIFY_SIGS=$(git config --get gentoo.verify-signatures)
: "${VERIFY_SIGS:=gentoo-devs}"

: "${GL_USER:=}" # Should come from the gitolite
: "${gpg_fpr:=}" # Should come from the gitolite hook

case ${VERIFY_SIGS} in

	gentoo-devs)
		if [[ ${GL_USER} != *@gentoo.org ]]; then
			echo "*** Pusher address is not @gentoo.org" >&2
			echo "    (it is ${GL_USER})" >&2
			echo "*** Please report this to infra ($0)" >&2
			exit 1
		fi

		# find key fingerprints in LDAP
		KEY_FPS=$(ldapsearch "uid=${GL_USER%@gentoo.org}" -D '' -Z -LLL \
			gpgfingerprint -o ldif-wrap=no | \
			sed -n -e '/^gpgfingerprint: /{s/^.*://;s/ //g;p}')
		# verify GLEP63 compliance
		GOOD_KEYS=()
		HAVE_NONCOMPLIANT=no
		for K in ${KEY_FPS}; do
			LC_CTYPE=en_US.UTF-8 \
			glep63-check -S glep63-2 -k "${K}" &&
				GOOD_KEYS+=( "${K}" ) ||
				HAVE_NONCOMPLIANT=yes
		done
		if [[ ${#GOOD_KEYS[@]} -eq 0 ]]; then
			echo "*** None of your keys comply with GLEP 63." >&2
			echo "    Please update the keys into conformance if you wish to continue" >&2
			echo "    using them. If not, please remove unused keys from LDAP." >&2
			exit 1
		elif [[ ${HAVE_NONCOMPLIANT} == yes ]]; then
			echo "*** Warning. One or more OpenPGP keys do not comply with GLEP 63." >&2
			echo "    Please update the keys into conformance if you wish to continue" >&2
			echo "    using them. If not, please remove unused keys from LDAP." >&2
		fi

		# create a dedicated GNUPGHOME
		TMPHOME=$(mktemp -d)
		trap 'rm -rf "${TMPHOME}"' EXIT

		# transfer the keys:
		# - ONLY for the developer/service in question
		# - with chain to L1
		CHAIN_L1=(
			ABD00913019D6354BA1D9A132839FE0D796198B1 # openpgp-auth+l1@gentoo.org
		)
		CHAIN_L2=(
			2C13823B8237310FA213034930D132FF0FF50EEB # openpgp-auth+l2-dev@gentoo.org
			18F703D702B1B9591373148C55D3238EC050396E # openpgp-auth+l2-srv@gentoo.org
		)
		EXPORT_CMD=(
			gpg -q
			--export-options 'export-clean,no-export-local-sigs,no-export-attributes'
			# keep-uid filter is not working in gnupg-2.4.3 or gnupg-2.2.41
			#--export-filter 'keep-uid="uid =~ @gentoo.org"'
			--export
			)
		IMPORT_CMD=(
			gpg -q
			# no-self-sigs-only is needed to import the signature chain.
			--import-options 'import-clean,no-import-local-sigs,no-keep-ownertrust,no-self-sigs-only'
			# keep-uid filter is not working in gnupg-2.4.3 or gnupg-2.2.41
			#--import-filter 'keep-uid="uid =~ @gentoo.org"'
			--import
		)
		"${EXPORT_CMD[@]}" \
			"${CHAIN_L1[@]}" \
			"${CHAIN_L2[@]}" \
			"${GOOD_KEYS[@]}" \
		| \
		GNUPGHOME=${TMPHOME} \
			"${IMPORT_CMD[@]}"

		# use new GNUGPHOME to restrict to dev's keys
		GNUPGHOME=${TMPHOME}
		export GNUPGHOME=${TMPHOME}
		cat >>"$GNUPGHOME"/gpg.conf <<-EOF
		# Explicitly set trust model;
		# git-2.43 does not recognize the always & direct trust model behavior
		# so it exports that the signatures came from an untrusted key
		trust-model pgp
		EOF
		# And declare that the L1 key is trusted.
		# This could go into the trustdb file instead, but cleaner this way.
		for _k in "${CHAIN_L1[@]}" ; do
		  echo "trusted-key $_k" >>"$GNUPGHOME"/gpg.conf
		done

		# If there are problems w/ the key export/import loop, or trust
		# verification; dump here
		#GNUPGHOME=${TMPHOME} gpg --check-trustdb
		#GNUPGHOME=${TMPHOME} gpg --list-sig
		;;

	keys.gentoo.org)
		GNUPGHOME=$(readlink -f ~git/.gnupg-sks/)
		export GNUPGHOME
		# Valid keys for this user
		KEY_FPS=${gpg_fpr}
		if [[ -z "$KEY_FPS" ]]; then
			fail_signed_push "No keys configured in Gitolite for $GL_USER"
		fi
		# create a dedicated GNUPGHOME
		TMPHOME=$(mktemp -d)
		trap 'rm -rf "${TMPHOME}"' EXIT
		EXPORT_CMD=(
			gpg -q
			--export-options 'export-clean,no-export-local-sigs,no-export-attributes'
			# keep-uid filter is not working in gnupg-2.4.3 or gnupg-2.2.41
			#--export-filter 'keep-uid="uid =~ @gentoo.org"'
			--export
			)
		IMPORT_CMD=(
			gpg -q
			# no-self-sigs-only is needed to import the signature chain.
			--import-options 'import-clean,no-import-local-sigs,no-keep-ownertrust,no-self-sigs-only'
			# keep-uid filter is not working in gnupg-2.4.3 or gnupg-2.2.41
			#--import-filter 'keep-uid="uid =~ @gentoo.org"'
			--import
		)
		mapfile -t GOOD_KEYS < <(fmt -1 <<<"${gpg_fpr//,/ }")
		"${EXPORT_CMD[@]}" \
			"${GOOD_KEYS[@]}" \
		| \
		GNUPGHOME=${TMPHOME} \
			"${IMPORT_CMD[@]}"
		# use new GNUGPHOME to restrict to dev's keys
		GNUPGHOME=${TMPHOME}
		export GNUPGHOME
		cat >>"$GNUPGHOME"/gpg.conf <<-EOF
		# Explicitly set trust model;
		# git-2.43 does not recognize the always & direct trust model behavior
		# so it exports that the signatures came from an untrusted key
		trust-model pgp
		EOF
		# And declare that the declared Git keys are trusted
		# This could go into the trustdb file instead, but cleaner this way.
		for _k in "${GOOD_KEYS[@]}" ; do
		  echo "trusted-key $_k" >> "$GNUPGHOME"/gpg.conf
		done
		;;
	no)
		;;
	*)
		echo "Invalid value of gentoo.verify-signatures" >&2
		exit 1
esac

case ${SIGNED_BRANCHES} in
	all-refs)
		;;
	all)
		[[ ${refname} == refs/heads/* ]] || exit 0
		;;
	*)
		[[ ${refname} == refs/heads/* ]] || exit 0
		branch_found=
		for branch in ${SIGNED_BRANCHES}; do
			if [[ ${refname#refs/heads/} == "${branch}" ]]; then
				branch_found=1
				break
			fi
		done
		[[ ${branch_found} == 1 ]] || exit 0
esac

IFS='
'

# special cases
zeros=0000000000000000000000000000000000000000
# branch removal
[[ ${newrev} == "${zeros}" ]] && exit 0
# new branch; try to find a merge base with master
if [[ ${oldrev} == "${zeros}" && ${refname} != refs/heads/master ]]; then
	mergebase=$(git merge-base refs/heads/master "${newrev}")
	[[ -n ${mergebase} ]] && oldrev=${mergebase}
fi
rev_list_arg="${oldrev}..${newrev}"
# new and no common commit?  gotta check them all
[[ ${oldrev} == "${zeros}" ]] && rev_list_arg="${newrev}"

while read -r r; do
	committer=$(git show -q --pretty=format:'%ce' "${r}")
	if [[ ${VERIFY_SIGS} == gentoo-devs && ${committer} != *@gentoo.org ]]; then
		echo "*** Committer address is not @gentoo.org, refusing"
		exit 1
	fi

	signst=$(git show -q --pretty=format:'%G?' "${r}")
	case ${VERIFY_SIGS} in
		gentoo-devs|keys.gentoo.org)
			# gentoo dev signatures must be Good
			[[ ${signst} == G ]] && continue
			;;
		no)
			# additionally skip untrusted/impossible to check
			# when verification is disabled
			[[ ${signst} == [GUE] ]] && continue
			;;
	esac

	# error reporting
	case ${signst} in
		U)
			echo "*** Untrusted signature on ${r}, refusing"
			exit 1
			;;
		B)
			echo "*** Bad signature on ${r}, refusing"
			exit 1
			;;
		N)
			echo "*** No signature on ${r}, refusing"
			exit 1
			;;
		E)
			echo "*** Signature cannot be checked on ${r}, refusing"
			exit 1
			;;
		*)
			echo "*** Unknown signature status '${signst}', refusing"
			exit 1
			;;
	esac
done < <(git rev-list --first-parent "${rev_list_arg}")

# --- Finished
exit 0