1 |
It is useful for verifying distfiles that come from OpenBSD folks since |
2 |
signify produces signatures incompatible with GnuPG. |
3 |
|
4 |
Signed-off-by: Anna Vyalkova <cyber+gentoo@×××××.in> |
5 |
--- |
6 |
Feel free to edit this patch or leave suggestions if you think I've done |
7 |
something wrong. |
8 |
|
9 |
I've tested this eclass with both gnupg and signify signatures but more |
10 |
testing won't hurt (to make sure I didn't break anything). |
11 |
|
12 |
If this patch is accepted, some developer will need to commit it. |
13 |
|
14 |
eclass/verify-sig.eclass | 138 +++++++++++++++++++++++++++++---------- |
15 |
1 file changed, 105 insertions(+), 33 deletions(-) |
16 |
|
17 |
diff --git a/eclass/verify-sig.eclass b/eclass/verify-sig.eclass |
18 |
index 2bc5bd5ddba..d3d2326a3bc 100644 |
19 |
--- a/eclass/verify-sig.eclass |
20 |
+++ b/eclass/verify-sig.eclass |
21 |
@@ -1,265 +1,337 @@ |
22 |
# Copyright 2020-2021 Gentoo Authors |
23 |
# Distributed under the terms of the GNU General Public License v2 |
24 |
|
25 |
# @ECLASS: verify-sig.eclass |
26 |
# @MAINTAINER: |
27 |
# Michał Górny <mgorny@g.o> |
28 |
# @SUPPORTED_EAPIS: 7 8 |
29 |
# @BLURB: Eclass to verify upstream signatures on distfiles |
30 |
# @DESCRIPTION: |
31 |
# verify-sig eclass provides a streamlined approach to verifying |
32 |
# upstream signatures on distfiles. Its primary purpose is to permit |
33 |
# developers to easily verify signatures while bumping packages. |
34 |
# The eclass removes the risk of developer forgetting to perform |
35 |
# the verification, or performing it incorrectly, e.g. due to additional |
36 |
# keys in the local keyring. It also permits users to verify |
37 |
# the developer's work. |
38 |
# |
39 |
# To use the eclass, start by packaging the upstream's key |
40 |
# as app-crypt/openpgp-keys-*. Then inherit the eclass, add detached |
41 |
# signatures to SRC_URI and set VERIFY_SIG_OPENPGP_KEY_PATH. The eclass |
42 |
# provides verify-sig USE flag to toggle the verification. |
43 |
# |
44 |
+# If you need to use signify, you may want to copy distfiles into WORKDIR to |
45 |
+# work around "Too many levels of symbolic links" error. |
46 |
+# @EXAMPLE: |
47 |
# Example use: |
48 |
+# |
49 |
# @CODE |
50 |
# inherit verify-sig |
51 |
# |
52 |
# SRC_URI="https://example.org/${P}.tar.gz |
53 |
# verify-sig? ( https://example.org/${P}.tar.gz.sig )" |
54 |
# BDEPEND=" |
55 |
# verify-sig? ( app-crypt/openpgp-keys-example )" |
56 |
# |
57 |
# VERIFY_SIG_OPENPGP_KEY_PATH=${BROOT}/usr/share/openpgp-keys/example.asc |
58 |
# @CODE |
59 |
|
60 |
case ${EAPI} in |
61 |
7|8) ;; |
62 |
*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; |
63 |
esac |
64 |
|
65 |
EXPORT_FUNCTIONS src_unpack |
66 |
|
67 |
if [[ ! ${_VERIFY_SIG_ECLASS} ]]; then |
68 |
|
69 |
IUSE="verify-sig" |
70 |
|
71 |
-BDEPEND=" |
72 |
- verify-sig? ( |
73 |
- app-crypt/gnupg |
74 |
- >=app-portage/gemato-16 |
75 |
- )" |
76 |
+# @ECLASS-VARIABLE: VERIFY_SIG_IMPL |
77 |
+# @PRE_INHERIT |
78 |
+# @DESCRIPTION: |
79 |
+# OpenPGP implementation to use. Valid options: "gnupg" and "signify". |
80 |
+: ${VERIFY_SIG_IMPL:=gnupg} |
81 |
+ |
82 |
+case ${VERIFY_SIG_IMPL} in |
83 |
+ gnupg) |
84 |
+ BDEPEND=" |
85 |
+ verify-sig? ( |
86 |
+ app-crypt/gnupg |
87 |
+ >=app-portage/gemato-16 |
88 |
+ )" |
89 |
+ ;; |
90 |
+ signify) |
91 |
+ BDEPEND="verify-sig? ( app-crypt/signify )" |
92 |
+ ;; |
93 |
+ *) |
94 |
+ die "${ECLASS}: unknown OpenPGP implementation '${VERIFY_SIG_IMPL}'" |
95 |
+ ;; |
96 |
+esac |
97 |
|
98 |
# @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEY_PATH |
99 |
# @DEFAULT_UNSET |
100 |
# @DESCRIPTION: |
101 |
# Path to key bundle used to perform the verification. This is required |
102 |
# when using default src_unpack. Alternatively, the key path can be |
103 |
# passed directly to the verification functions. |
104 |
|
105 |
# @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEYSERVER |
106 |
# @DEFAULT_UNSET |
107 |
# @DESCRIPTION: |
108 |
# Keyserver used to refresh keys. If not specified, the keyserver |
109 |
# preference from the key will be respected. If no preference |
110 |
-# is specified by the key, the GnuPG default will be used. |
111 |
+# is specified by the key, the GnuPG default will be used. Supported for GnuPG |
112 |
+# only. |
113 |
|
114 |
# @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEY_REFRESH |
115 |
# @USER_VARIABLE |
116 |
# @DESCRIPTION: |
117 |
# Attempt to refresh keys via WKD/keyserver. Set it to "yes" |
118 |
# in make.conf to enable. Note that this requires working Internet |
119 |
-# connection. |
120 |
+# connection. Supported for GnuPG only. |
121 |
: ${VERIFY_SIG_OPENPGP_KEY_REFRESH:=no} |
122 |
|
123 |
# @FUNCTION: verify-sig_verify_detached |
124 |
# @USAGE: <file> <sig-file> [<key-file>] |
125 |
# @DESCRIPTION: |
126 |
# Read the detached signature from <sig-file> and verify <file> against |
127 |
# it. <key-file> can either be passed directly, or it defaults |
128 |
# to VERIFY_SIG_OPENPGP_KEY_PATH. The function dies if verification |
129 |
# fails. |
130 |
verify-sig_verify_detached() { |
131 |
local file=${1} |
132 |
local sig=${2} |
133 |
local key=${3:-${VERIFY_SIG_OPENPGP_KEY_PATH}} |
134 |
|
135 |
[[ -n ${key} ]] || |
136 |
die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset" |
137 |
|
138 |
local extra_args=() |
139 |
[[ ${VERIFY_SIG_OPENPGP_KEY_REFRESH} == yes ]] || extra_args+=( -R ) |
140 |
- [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]] && extra_args+=( |
141 |
- --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}" |
142 |
- ) |
143 |
+ if [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]]; then |
144 |
+ [[ ${VERIFY_SIG_IMPL} == gnupg ]] || |
145 |
+ die "${FUNCNAME}: VERIFY_SIG_OPENPGP_KEYSERVER is not supported" |
146 |
+ |
147 |
+ extra_args+=( |
148 |
+ --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}" |
149 |
+ ) |
150 |
+ fi |
151 |
|
152 |
# GPG upstream knows better than to follow the spec, so we can't |
153 |
# override this directory. However, there is a clean fallback |
154 |
# to GNUPGHOME. |
155 |
addpredict /run/user |
156 |
|
157 |
local filename=${file##*/} |
158 |
[[ ${file} == - ]] && filename='(stdin)' |
159 |
einfo "Verifying ${filename} ..." |
160 |
- gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \ |
161 |
- gpg --verify "${sig}" "${file}" || |
162 |
- die "PGP signature verification failed" |
163 |
+ case ${VERIFY_SIG_IMPL} in |
164 |
+ gnupg) |
165 |
+ gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \ |
166 |
+ gpg --verify "${sig}" "${file}" || |
167 |
+ die "PGP signature verification failed" |
168 |
+ ;; |
169 |
+ signify) |
170 |
+ signify -V -p "${key}" -m "${file}" -x "${sig}" || |
171 |
+ die "PGP signature verification failed" |
172 |
+ ;; |
173 |
+ esac |
174 |
} |
175 |
|
176 |
# @FUNCTION: verify-sig_verify_message |
177 |
# @USAGE: <file> <output-file> [<key-file>] |
178 |
# @DESCRIPTION: |
179 |
# Verify that the file ('-' for stdin) contains a valid, signed PGP |
180 |
# message and write the message into <output-file> ('-' for stdout). |
181 |
# <key-file> can either be passed directly, or it defaults |
182 |
# to VERIFY_SIG_OPENPGP_KEY_PATH. The function dies if verification |
183 |
# fails. Note that using output from <output-file> is important as it |
184 |
# prevents the injection of unsigned data. |
185 |
verify-sig_verify_message() { |
186 |
local file=${1} |
187 |
local output_file=${2} |
188 |
local key=${3:-${VERIFY_SIG_OPENPGP_KEY_PATH}} |
189 |
|
190 |
[[ -n ${key} ]] || |
191 |
die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset" |
192 |
|
193 |
local extra_args=() |
194 |
[[ ${VERIFY_SIG_OPENPGP_KEY_REFRESH} == yes ]] || extra_args+=( -R ) |
195 |
- [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]] && extra_args+=( |
196 |
- --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}" |
197 |
- ) |
198 |
+ if [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]]; then |
199 |
+ [[ ${VERIFY_SIG_IMPL} == gnupg ]] || |
200 |
+ die "${FUNCNAME}: VERIFY_SIG_OPENPGP_KEYSERVER is not supported" |
201 |
+ |
202 |
+ extra_args+=( |
203 |
+ --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}" |
204 |
+ ) |
205 |
+ fi |
206 |
|
207 |
# GPG upstream knows better than to follow the spec, so we can't |
208 |
# override this directory. However, there is a clean fallback |
209 |
# to GNUPGHOME. |
210 |
addpredict /run/user |
211 |
|
212 |
local filename=${file##*/} |
213 |
[[ ${file} == - ]] && filename='(stdin)' |
214 |
einfo "Verifying ${filename} ..." |
215 |
- gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \ |
216 |
- gpg --verify --output="${output_file}" "${file}" || |
217 |
- die "PGP signature verification failed" |
218 |
+ case ${VERIFY_SIG_IMPL} in |
219 |
+ gnupg) |
220 |
+ gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \ |
221 |
+ gpg --verify --output="${output_file}" "${file}" || |
222 |
+ die "PGP signature verification failed" |
223 |
+ ;; |
224 |
+ signify) |
225 |
+ signify -V -e -p "${key}" -m "${output_file}" -x "${file}" || |
226 |
+ die "PGP signature verification failed" |
227 |
+ ;; |
228 |
+ esac |
229 |
} |
230 |
|
231 |
-# @FUNCTION: verify-sig_verify_signed_checksums |
232 |
+# @FUNCTION: _gpg_verify_signed_checksums |
233 |
+# @INTERNAL |
234 |
# @USAGE: <checksum-file> <algo> <files> [<key-file>] |
235 |
# @DESCRIPTION: |
236 |
-# Verify the checksums for all files listed in the space-separated list |
237 |
-# <files> (akin to ${A}) using a PGP-signed <checksum-file>. <algo> |
238 |
-# specified the checksum algorithm (e.g. sha256). <key-file> can either |
239 |
-# be passed directly, or it defaults to VERIFY_SIG_OPENPGP_KEY_PATH. |
240 |
-# |
241 |
-# The function dies if PGP verification fails, the checksum file |
242 |
-# contains unsigned data, one of the files do not match checksums |
243 |
-# or are missing from the checksum file. |
244 |
-verify-sig_verify_signed_checksums() { |
245 |
+# GnuPG-specific function to verify a signed checksums list. |
246 |
+_gpg_verify_signed_checksums() { |
247 |
local checksum_file=${1} |
248 |
local algo=${2} |
249 |
local files=() |
250 |
read -r -d '' -a files <<<"${3}" |
251 |
local key=${4:-${VERIFY_SIG_OPENPGP_KEY_PATH}} |
252 |
- |
253 |
local chksum_prog chksum_len |
254 |
+ |
255 |
case ${algo} in |
256 |
sha256) |
257 |
chksum_prog=sha256sum |
258 |
chksum_len=64 |
259 |
;; |
260 |
*) |
261 |
die "${FUNCNAME}: unknown checksum algo ${algo}" |
262 |
;; |
263 |
esac |
264 |
|
265 |
- [[ -n ${key} ]] || |
266 |
- die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset" |
267 |
- |
268 |
local checksum filename junk ret=0 count=0 |
269 |
while read -r checksum filename junk; do |
270 |
[[ ${#checksum} -eq ${chksum_len} ]] || continue |
271 |
[[ -z ${checksum//[0-9a-f]} ]] || continue |
272 |
has "${filename}" "${files[@]}" || continue |
273 |
[[ -z ${junk} ]] || continue |
274 |
|
275 |
"${chksum_prog}" -c --strict - <<<"${checksum} ${filename}" |
276 |
if [[ ${?} -eq 0 ]]; then |
277 |
(( count++ )) |
278 |
else |
279 |
ret=1 |
280 |
fi |
281 |
done < <(verify-sig_verify_message "${checksum_file}" - "${key}") |
282 |
|
283 |
[[ ${ret} -eq 0 ]] || |
284 |
die "${FUNCNAME}: at least one file did not verify successfully" |
285 |
[[ ${count} -eq ${#files[@]} ]] || |
286 |
die "${FUNCNAME}: checksums for some of the specified files were missing" |
287 |
} |
288 |
|
289 |
+# @FUNCTION: verify-sig_verify_signed_checksums |
290 |
+# @USAGE: <checksum-file> <algo> <files> [<key-file>] |
291 |
+# @DESCRIPTION: |
292 |
+# Verify the checksums for all files listed in the space-separated list |
293 |
+# <files> (akin to ${A}) using a PGP-signed <checksum-file>. <algo> |
294 |
+# specified the checksum algorithm (e.g. sha256). <key-file> can either |
295 |
+# be passed directly, or it defaults to VERIFY_SIG_OPENPGP_KEY_PATH. |
296 |
+# |
297 |
+# The function dies if PGP verification fails, the checksum file |
298 |
+# contains unsigned data, one of the files do not match checksums |
299 |
+# or are missing from the checksum file. |
300 |
+verify-sig_verify_signed_checksums() { |
301 |
+ local checksum_file=${1} |
302 |
+ local algo=${2} |
303 |
+ local files=() |
304 |
+ read -r -d '' -a files <<<"${3}" |
305 |
+ local key=${4:-${VERIFY_SIG_OPENPGP_KEY_PATH}} |
306 |
+ |
307 |
+ [[ -n ${key} ]] || |
308 |
+ die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset" |
309 |
+ |
310 |
+ case ${VERIFY_SIG_IMPL} in |
311 |
+ gnupg) |
312 |
+ _gpg_verify_signed_checksums \ |
313 |
+ "${checksum_file}" "${algo}" "${files[@]}" "${key}" |
314 |
+ ;; |
315 |
+ signify) |
316 |
+ signify -C -p "${key}" \ |
317 |
+ -x "${checksum_file}" "${files[@]}" || |
318 |
+ die "PGP signature verification failed" |
319 |
+ ;; |
320 |
+ esac |
321 |
+} |
322 |
+ |
323 |
# @FUNCTION: verify-sig_src_unpack |
324 |
# @DESCRIPTION: |
325 |
# Default src_unpack override that verifies signatures for all |
326 |
# distfiles if 'verify-sig' flag is enabled. The function dies if any |
327 |
# of the signatures fails to verify or if any distfiles are not signed. |
328 |
# Please write src_unpack() yourself if you need to perform partial |
329 |
# verification. |
330 |
verify-sig_src_unpack() { |
331 |
if use verify-sig; then |
332 |
local f suffix found |
333 |
local distfiles=() signatures=() nosigfound=() straysigs=() |
334 |
|
335 |
# find all distfiles and signatures, and combine them |
336 |
for f in ${A}; do |
337 |
found= |
338 |
for suffix in .asc .sig; do |
339 |
if [[ ${f} == *${suffix} ]]; then |
340 |
signatures+=( "${f}" ) |
341 |
found=sig |
342 |
break |
343 |
else |
344 |
if has "${f}${suffix}" ${A}; then |
345 |
distfiles+=( "${f}" ) |
346 |
found=dist+sig |
347 |
break |
348 |
fi |
349 |
fi |
350 |
done |
351 |
if [[ ! ${found} ]]; then |
352 |
nosigfound+=( "${f}" ) |
353 |
fi |
354 |
done |
355 |
|
356 |
# check if all distfiles are signed |
357 |
if [[ ${#nosigfound[@]} -gt 0 ]]; then |
358 |
eerror "The following distfiles lack detached signatures:" |
359 |
for f in "${nosigfound[@]}"; do |
360 |
eerror " ${f}" |
361 |
done |
362 |
die "Unsigned distfiles found" |
363 |
fi |
364 |
|
365 |
# check if there are no stray signatures |
366 |
for f in "${signatures[@]}"; do |
367 |
if ! has "${f%.*}" "${distfiles[@]}"; then |
368 |
straysigs+=( "${f}" ) |
369 |
fi |
370 |
done |
371 |
if [[ ${#straysigs[@]} -gt 0 ]]; then |
372 |
eerror "The following signatures do not match any distfiles:" |
373 |
for f in "${straysigs[@]}"; do |
374 |
eerror " ${f}" |
375 |
done |
376 |
die "Unused signatures found" |
377 |
fi |
378 |
|
379 |
# now perform the verification |
380 |
for f in "${signatures[@]}"; do |
381 |
verify-sig_verify_detached \ |
382 |
"${DISTDIR}/${f%.*}" "${DISTDIR}/${f}" |
383 |
done |
384 |
fi |
385 |
|
386 |
# finally, unpack the distfiles |
387 |
default_src_unpack |
388 |
} |
389 |
|
390 |
_VERIFY_SIG_ECLASS=1 |
391 |
fi |
392 |
-- |
393 |
2.34.1 |