Gentoo Archives: gentoo-dev

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

Replies