Gentoo Archives: gentoo-dev

From: "Michał Górny" <mgorny@g.o>
To: gentoo-dev@l.g.o
Subject: Re: [gentoo-dev] [PATCH v2] verify-sig.eclass: add app-crypt/signify support
Date: Thu, 09 Dec 2021 11:28:28
Message-Id: ed73e8a76a5630f9754a7cd43ea47f5f1f5d0075.camel@gentoo.org
In Reply to: [gentoo-dev] [PATCH v2] verify-sig.eclass: add app-crypt/signify support by Anna Vyalkova
1 On Thu, 2021-12-09 at 14:42 +0500, Anna Vyalkova wrote:
2 > It is useful for verifying distfiles that come from OpenBSD folks, since
3 > signify produces signatures incompatible with GnuPG.
4
5 For the record, I don't like the fact that OpenBSD reinvents the wheel
6 but I'm not going to oppose the patch.
7
8 >
9 > Signed-off-by: Anna Vyalkova <cyber+gentoo@×××××.in>
10 > ---
11 > Changes from previous patch:
12 > - wording
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..b3e6eb131a4 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 > +# Signature verification utility to use. Valid options: "gnupg" and "signify".
80 > +: ${VERIFY_SIG_IMPL:=gnupg}
81
82 Make this "openpgp", please. A future version may not be using GnuPG
83 anymore but the standard will remain.
84
85 Does "signify" have some backing standard name too?
86
87 > +
88 > +case ${VERIFY_SIG_IMPL} in
89 > + gnupg)
90 > + BDEPEND="
91 > + verify-sig? (
92 > + app-crypt/gnupg
93 > + >=app-portage/gemato-16
94 > + )"
95 > + ;;
96 > + signify)
97 > + BDEPEND="verify-sig? ( app-crypt/signify )"
98 > + ;;
99 > + *)
100 > + die "${ECLASS}: unknown implementation '${VERIFY_SIG_IMPL}'"
101 > + ;;
102 > +esac
103 >
104 > # @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEY_PATH
105 > # @DEFAULT_UNSET
106 > # @DESCRIPTION:
107 > # Path to key bundle used to perform the verification. This is required
108 > # when using default src_unpack. Alternatively, the key path can be
109 > # passed directly to the verification functions.
110 >
111 > # @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEYSERVER
112 > # @DEFAULT_UNSET
113 > # @DESCRIPTION:
114 > # Keyserver used to refresh keys. If not specified, the keyserver
115 > # preference from the key will be respected. If no preference
116 > -# is specified by the key, the GnuPG default will be used.
117 > +# is specified by the key, the GnuPG default will be used. Supported for GnuPG
118 > +# only.
119 >
120 > # @ECLASS-VARIABLE: VERIFY_SIG_OPENPGP_KEY_REFRESH
121 > # @USER_VARIABLE
122 > # @DESCRIPTION:
123 > # Attempt to refresh keys via WKD/keyserver. Set it to "yes"
124 > # in make.conf to enable. Note that this requires working Internet
125 > -# connection.
126 > +# connection. Supported for GnuPG only.
127 > : ${VERIFY_SIG_OPENPGP_KEY_REFRESH:=no}
128 >
129 > # @FUNCTION: verify-sig_verify_detached
130 > # @USAGE: <file> <sig-file> [<key-file>]
131 > # @DESCRIPTION:
132 > # Read the detached signature from <sig-file> and verify <file> against
133 > # it. <key-file> can either be passed directly, or it defaults
134 > # to VERIFY_SIG_OPENPGP_KEY_PATH. The function dies if verification
135 > # fails.
136 > verify-sig_verify_detached() {
137 > local file=${1}
138 > local sig=${2}
139 > local key=${3:-${VERIFY_SIG_OPENPGP_KEY_PATH}}
140 >
141 > [[ -n ${key} ]] ||
142 > die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset"
143 >
144 > local extra_args=()
145 > [[ ${VERIFY_SIG_OPENPGP_KEY_REFRESH} == yes ]] || extra_args+=( -R )
146 > - [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]] && extra_args+=(
147 > - --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}"
148 > - )
149 > + if [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]]; then
150 > + [[ ${VERIFY_SIG_IMPL} == gnupg ]] ||
151 > + die "${FUNCNAME}: VERIFY_SIG_OPENPGP_KEYSERVER is not supported"
152 > +
153 > + extra_args+=(
154 > + --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}"
155 > + )
156 > + fi
157 >
158 > # GPG upstream knows better than to follow the spec, so we can't
159 > # override this directory. However, there is a clean fallback
160 > # to GNUPGHOME.
161 > addpredict /run/user
162 >
163 > local filename=${file##*/}
164 > [[ ${file} == - ]] && filename='(stdin)'
165 > einfo "Verifying ${filename} ..."
166 > - gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \
167 > - gpg --verify "${sig}" "${file}" ||
168 > - die "PGP signature verification failed"
169 > + case ${VERIFY_SIG_IMPL} in
170 > + gnupg)
171 > + gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \
172 > + gpg --verify "${sig}" "${file}" ||
173 > + die "PGP signature verification failed"
174 > + ;;
175 > + signify)
176 > + signify -V -p "${key}" -m "${file}" -x "${sig}" ||
177 > + die "Signify signature verification failed"
178 > + ;;
179 > + esac
180 > }
181 >
182 > # @FUNCTION: verify-sig_verify_message
183 > # @USAGE: <file> <output-file> [<key-file>]
184 > # @DESCRIPTION:
185 > # Verify that the file ('-' for stdin) contains a valid, signed PGP
186 > # message and write the message into <output-file> ('-' for stdout).
187 > # <key-file> can either be passed directly, or it defaults
188 > # to VERIFY_SIG_OPENPGP_KEY_PATH. The function dies if verification
189 > # fails. Note that using output from <output-file> is important as it
190 > # prevents the injection of unsigned data.
191 > verify-sig_verify_message() {
192 > local file=${1}
193 > local output_file=${2}
194 > local key=${3:-${VERIFY_SIG_OPENPGP_KEY_PATH}}
195 >
196 > [[ -n ${key} ]] ||
197 > die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset"
198 >
199 > local extra_args=()
200 > [[ ${VERIFY_SIG_OPENPGP_KEY_REFRESH} == yes ]] || extra_args+=( -R )
201 > - [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]] && extra_args+=(
202 > - --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}"
203 > - )
204 > + if [[ -n ${VERIFY_SIG_OPENPGP_KEYSERVER+1} ]]; then
205 > + [[ ${VERIFY_SIG_IMPL} == gnupg ]] ||
206 > + die "${FUNCNAME}: VERIFY_SIG_OPENPGP_KEYSERVER is not supported"
207 > +
208 > + extra_args+=(
209 > + --keyserver "${VERIFY_SIG_OPENPGP_KEYSERVER}"
210 > + )
211 > + fi
212 >
213 > # GPG upstream knows better than to follow the spec, so we can't
214 > # override this directory. However, there is a clean fallback
215 > # to GNUPGHOME.
216 > addpredict /run/user
217 >
218 > local filename=${file##*/}
219 > [[ ${file} == - ]] && filename='(stdin)'
220 > einfo "Verifying ${filename} ..."
221 > - gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \
222 > - gpg --verify --output="${output_file}" "${file}" ||
223 > - die "PGP signature verification failed"
224 > + case ${VERIFY_SIG_IMPL} in
225 > + gnupg)
226 > + gemato gpg-wrap -K "${key}" "${extra_args[@]}" -- \
227 > + gpg --verify --output="${output_file}" "${file}" ||
228 > + die "PGP signature verification failed"
229 > + ;;
230 > + signify)
231 > + signify -V -e -p "${key}" -m "${output_file}" -x "${file}" ||
232 > + die "Signify signature verification failed"
233 > + ;;
234 > + esac
235 > }
236 >
237 > -# @FUNCTION: verify-sig_verify_signed_checksums
238 > +# @FUNCTION: _gpg_verify_signed_checksums
239 > +# @INTERNAL
240 > # @USAGE: <checksum-file> <algo> <files> [<key-file>]
241 > # @DESCRIPTION:
242 > -# Verify the checksums for all files listed in the space-separated list
243 > -# <files> (akin to ${A}) using a PGP-signed <checksum-file>. <algo>
244 > -# specified the checksum algorithm (e.g. sha256). <key-file> can either
245 > -# be passed directly, or it defaults to VERIFY_SIG_OPENPGP_KEY_PATH.
246 > -#
247 > -# The function dies if PGP verification fails, the checksum file
248 > -# contains unsigned data, one of the files do not match checksums
249 > -# or are missing from the checksum file.
250 > -verify-sig_verify_signed_checksums() {
251 > +# GnuPG-specific function to verify a signed checksums list.
252 > +_gpg_verify_signed_checksums() {
253 > local checksum_file=${1}
254 > local algo=${2}
255 > local files=()
256 > read -r -d '' -a files <<<"${3}"
257 > local key=${4:-${VERIFY_SIG_OPENPGP_KEY_PATH}}
258 > -
259 > local chksum_prog chksum_len
260 > +
261 > case ${algo} in
262 > sha256)
263 > chksum_prog=sha256sum
264 > chksum_len=64
265 > ;;
266 > *)
267 > die "${FUNCNAME}: unknown checksum algo ${algo}"
268 > ;;
269 > esac
270 >
271 > - [[ -n ${key} ]] ||
272 > - die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset"
273 > -
274 > local checksum filename junk ret=0 count=0
275 > while read -r checksum filename junk; do
276 > [[ ${#checksum} -eq ${chksum_len} ]] || continue
277 > [[ -z ${checksum//[0-9a-f]} ]] || continue
278 > has "${filename}" "${files[@]}" || continue
279 > [[ -z ${junk} ]] || continue
280 >
281 > "${chksum_prog}" -c --strict - <<<"${checksum} ${filename}"
282 > if [[ ${?} -eq 0 ]]; then
283 > (( count++ ))
284 > else
285 > ret=1
286 > fi
287 > done < <(verify-sig_verify_message "${checksum_file}" - "${key}")
288 >
289 > [[ ${ret} -eq 0 ]] ||
290 > die "${FUNCNAME}: at least one file did not verify successfully"
291 > [[ ${count} -eq ${#files[@]} ]] ||
292 > die "${FUNCNAME}: checksums for some of the specified files were missing"
293 > }
294 >
295 > +# @FUNCTION: verify-sig_verify_signed_checksums
296 > +# @USAGE: <checksum-file> <algo> <files> [<key-file>]
297 > +# @DESCRIPTION:
298 > +# Verify the checksums for all files listed in the space-separated list
299 > +# <files> (akin to ${A}) using a signed <checksum-file>. <algo> specifies
300 > +# the checksum algorithm (e.g. sha256). <key-file> can either be passed
301 > +# directly, or it defaults to VERIFY_SIG_OPENPGP_KEY_PATH.
302 > +#
303 > +# The function dies if signature verification fails, the checksum file
304 > +# contains unsigned data, one of the files do not match checksums or
305 > +# are missing from the checksum file.
306 > +verify-sig_verify_signed_checksums() {
307 > + local checksum_file=${1}
308 > + local algo=${2}
309 > + local files=()
310 > + read -r -d '' -a files <<<"${3}"
311 > + local key=${4:-${VERIFY_SIG_OPENPGP_KEY_PATH}}
312 > +
313 > + [[ -n ${key} ]] ||
314 > + die "${FUNCNAME}: no key passed and VERIFY_SIG_OPENPGP_KEY_PATH unset"
315 > +
316 > + case ${VERIFY_SIG_IMPL} in
317 > + gnupg)
318 > + _gpg_verify_signed_checksums \
319 > + "${checksum_file}" "${algo}" "${files[@]}" "${key}"
320 > + ;;
321 > + signify)
322 > + signify -C -p "${key}" \
323 > + -x "${checksum_file}" "${files[@]}" ||
324 > + die "Signify signature verification failed"
325 > + ;;
326 > + esac
327 > +}
328 > +
329 > # @FUNCTION: verify-sig_src_unpack
330 > # @DESCRIPTION:
331 > # Default src_unpack override that verifies signatures for all
332 > # distfiles if 'verify-sig' flag is enabled. The function dies if any
333 > # of the signatures fails to verify or if any distfiles are not signed.
334 > # Please write src_unpack() yourself if you need to perform partial
335 > # verification.
336 > verify-sig_src_unpack() {
337 > if use verify-sig; then
338 > local f suffix found
339 > local distfiles=() signatures=() nosigfound=() straysigs=()
340 >
341 > # find all distfiles and signatures, and combine them
342 > for f in ${A}; do
343 > found=
344 > for suffix in .asc .sig; do
345 > if [[ ${f} == *${suffix} ]]; then
346 > signatures+=( "${f}" )
347 > found=sig
348 > break
349 > else
350 > if has "${f}${suffix}" ${A}; then
351 > distfiles+=( "${f}" )
352 > found=dist+sig
353 > break
354 > fi
355 > fi
356 > done
357 > if [[ ! ${found} ]]; then
358 > nosigfound+=( "${f}" )
359 > fi
360 > done
361 >
362 > # check if all distfiles are signed
363 > if [[ ${#nosigfound[@]} -gt 0 ]]; then
364 > eerror "The following distfiles lack detached signatures:"
365 > for f in "${nosigfound[@]}"; do
366 > eerror " ${f}"
367 > done
368 > die "Unsigned distfiles found"
369 > fi
370 >
371 > # check if there are no stray signatures
372 > for f in "${signatures[@]}"; do
373 > if ! has "${f%.*}" "${distfiles[@]}"; then
374 > straysigs+=( "${f}" )
375 > fi
376 > done
377 > if [[ ${#straysigs[@]} -gt 0 ]]; then
378 > eerror "The following signatures do not match any distfiles:"
379 > for f in "${straysigs[@]}"; do
380 > eerror " ${f}"
381 > done
382 > die "Unused signatures found"
383 > fi
384 >
385 > # now perform the verification
386 > for f in "${signatures[@]}"; do
387 > verify-sig_verify_detached \
388 > "${DISTDIR}/${f%.*}" "${DISTDIR}/${f}"
389 > done
390 > fi
391 >
392 > # finally, unpack the distfiles
393 > default_src_unpack
394 > }
395 >
396 > _VERIFY_SIG_ECLASS=1
397 > fi
398
399 --
400 Best regards,
401 Michał Górny