aboutsummaryrefslogtreecommitdiff
blob: e1f52137343097dd342932aa7c5558e72a922959 (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
#!/bin/bash
# gentoo-infra: infra/githooks.git:local/require-signed-push

VERIFY_SIGS=$(git config --get gentoo.verify-signatures)
: "${VERIFY_SIGS:=gentoo-devs}"

# ----------------------------------------------------------------------
# standard stuff
die() { echo "$@" >&2; exit 1; }
warn() { echo "$@" >&2; }

fail_signed_push() {
	warn "$@"
	warn "Your push was not signed with a known key."
	warn "You MUST use git push --signed with a known key."
	warn "Known keys are the subkeys of all primary keys in LDAP."
	warn "If you add a new (primary) key to LDAP, please ask Infra to sync gitolite."
	warn "If you modified your key and uploaded to keyservers, please wait 4 hours for sync (SKS pool is slow, keys.gentoo.org pool is faster)"
	warn "If you haven't done either of these things, please see https://wiki.gentoo.org/wiki/Project:Gentoo-keys/Generating_GLEP_63_based_OpenPGP_keys#Next_steps"
	warn "git-receive-pack variables:"
	for var in \
		GIT_PUSH_CERT \
		GIT_PUSH_CERT_KEY \
		GIT_PUSH_CERT_NONCE \
		GIT_PUSH_CERT_NONCE_SLOP \
		GIT_PUSH_CERT_NONCE_STATUS \
		GIT_PUSH_CERT_SIGNER \
		GIT_PUSH_CERT_STATUS \
		; do
			warn "$var='${!var}'"
	done
	if [ -n "${GIT_PUSH_CERT}" ]; then
		warn "A push-cert was found, and follows:"
		warn "====="
		git --no-pager show "$GIT_PUSH_CERT"
		warn "====="
	fi
	exit 1
}

log_git_push() {
	s=""
	for var in \
		GIT_PUSH_CERT \
		GIT_PUSH_CERT_KEY \
		GIT_PUSH_CERT_NONCE \
		GIT_PUSH_CERT_NONCE_SLOP \
		GIT_PUSH_CERT_NONCE_STATUS \
		GIT_PUSH_CERT_SIGNER \
		GIT_PUSH_CERT_STATUS \
		; do
			s="${s} $var='${!var}'"
	done
	logger -t require-signed-push -p info "require-signed-push${s}"
}

verify_committer_clock() {
	RAW_CERT="$(git show --format='pushtime %ct%nct %ct%nat %at%n%B' "$GIT_PUSH_CERT")"
	# Example inputs
	# Good clock (58 seconds delay on PIN entry to smartcard):
	# -------
	# pushtime 1468598038
	# certificate version 0.1
	# pusher 0x250B7AFED6379D85! 1468597981 +0200
	# nonce 1468597981-660c323b3137c0798711
	# -------
	# Bad clock, ~5 mins slow:
	# -------
	# pushtime 1468596921
	# certificate version 0.1
	# pusher 94BFDF4484AD142F 1468596642 +0200
	# nonce 1468596917-ac5118c996e285ace24e
	# -------

	# This is the time, according to server clock, that the server sent out to the user
	# also in GIT_PUSH_CERT_NONCE
	SERVER_NONCE_TIME="$(echo "$RAW_CERT" | awk '/^nonce /{sub("-.*","",$2); print $2}' )"
	# This is the time, according to USER clock, that the response was signed
	# We want the second to last field, because sometimes there are spaces!
	# pusher 94BFDF4484AD142F 1468596642 +0200
	# pusher Michael Palimaka <kensington@gentoo.org> 1468524562 +1000
	PUSHER_SIGN_TIME="$(echo "$RAW_CERT" | awk '/^pusher /{s=$(NF-1); print s}')"
	# This is the time, according to server clock, when the server got the response
	SERVER_PUSH_TIME="$(echo "$RAW_CERT" | awk '/^pushtime /{print $2}')"
	# But it might not be available...
	[[ -z "${SERVER_PUSH_TIME}" ]] && SERVER_PUSH_TIME=$(date +%s)

	[[ -z "$SERVER_NONCE_TIME" ]] && die "require-signed-push: Could not find push nonce"
	[[ -z "$PUSHER_SIGN_TIME" ]] && die "require-signed-push: Could not find pusher identity"

	T0="$SERVER_NONCE_TIME"
	T1="$PUSHER_SIGN_TIME"
	T2="$SERVER_PUSH_TIME"
	DELTA_T1_T0=$(( T1 - T0 ))
	DELTA_T2_T0=$(( T2 - T0 ))
	DELTA_T2_T1=$(( T2 - T1 ))
	[[ $DELTA_T1_T0 -lt 0 ]] && DELTA_T1_T0=$(( DELTA_T1_T0 * -1 ))
	[[ $DELTA_T2_T0 -lt 0 ]] && DELTA_T2_T0=$(( DELTA_T2_T0 * -1 ))
	[[ $DELTA_T2_T1 -lt 0 ]] && DELTA_T2_T1=$(( DELTA_T2_T1 * -1 ))

	CLOCK_DRIFT_LIMIT=5
	PUSH_LIMIT=60
	if [[ $DELTA_T1_T0 -ge $CLOCK_DRIFT_LIMIT ]]; then
		warn "Your system clock is off by $DELTA_T1_T0 seconds (limit $CLOCK_DRIFT_LIMIT)"
		die "Run NTP, rebase your commits as needed, and push again."
	fi
	if [[ $DELTA_T2_T0 -ge $PUSH_LIMIT ]]; then
		die "Try again! Your push took $DELTA_T2_T0 seconds, (limit $PUSH_LIMIT)."
	fi
}

# ----------------------------------------------------------------------
# Send info to syslog for debugging
log_git_push

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" >&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}') )
		# match signing key to the primary key
		PRIMARY_KEY=$(gpg --batch --with-colons --fingerprint "${GIT_PUSH_CERT_KEY}" \
			| sed -n -e '/^pub/{n;/^fpr/p}' | cut -d: -f10)
		if [[ -z ${PRIMARY_KEY} ]]; then
			fail_signed_push "Unable to identify primary key used for push"
		fi

		# primary key must match one of the fingerprints in LDAP
		if [[ " ${KEY_FPS[*]} " != *" ${PRIMARY_KEY} "* ]]; then
			fail_signed_push "Key used to sign the commit does not belong to ${GL_USER}"
		fi
		;;
	no)
		;;
	*)
		echo "Invalid value of gentoo.verify-signatures" >&2
		exit 1
esac

# Now validate
case $GIT_PUSH_CERT_STATUS in
	# Good
	G) ;;

	# Bad
	B) fail_signed_push "Bad signature" ;;

	# Untrusted good
	U) ;; # TODO: deny this later
	#U) fail_signed_push "Good but untrusted signature" ;;

	# No signature
	N)
		if [ -z "$GIT_PUSH_CERT" ]; then
			fail_signed_push "No signature found"
		else
			fail_signed_push "Push cert with no signature" # wtf?
		fi
		;;

	# Can't verify -- usually means unknown key
	E)
		if [[ ${VERIFY_SIGS} != no ]]; then
			fail_signed_push "Unknown OpenPGP key"
		fi
		;;

	# Future-proof
	*) fail_signed_push "Unknown GIT_PUSH_CERT_STATUS" ;;

esac

# Check the user clock as well.
verify_committer_clock

# All good now
exit 0