From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from lists.gentoo.org (pigeon.gentoo.org [208.92.234.80]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by finch.gentoo.org (Postfix) with ESMTPS id 9C1841581F3 for ; Wed, 27 Nov 2024 21:58:06 +0000 (UTC) Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 3F4C2E08DA; Wed, 27 Nov 2024 21:58:02 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [IPv6:2001:470:ea4a:1:5054:ff:fec7:86e4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by pigeon.gentoo.org (Postfix) with ESMTPS id 3F2CBE08A6 for ; Wed, 27 Nov 2024 21:58:01 +0000 (UTC) From: Sam James To: Eli Schwartz Cc: gentoo-dev@lists.gentoo.org Subject: Re: [gentoo-dev] [PATCH 1/2] sec-keys.eclass: new eclass In-Reply-To: <20241127203042.1503004-1-eschwartz@gentoo.org> (Eli Schwartz's message of "Wed, 27 Nov 2024 15:30:29 -0500") Organization: Gentoo References: <20241127203042.1503004-1-eschwartz@gentoo.org> User-Agent: mu4e 1.12.7; emacs 31.0.50 Date: Wed, 27 Nov 2024 21:57:57 +0000 Message-ID: <87plmgtk4a.fsf@gentoo.org> Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-dev@lists.gentoo.org Reply-to: gentoo-dev@lists.gentoo.org X-Auto-Response-Suppress: DR, RN, NRN, OOF, AutoReply MIME-Version: 1.0 Content-Type: text/plain X-Archives-Salt: 69ee02ce-45f4-427b-b090-5fc05883fa9d X-Archives-Hash: e4399500450ed8500da74fd465aeffac Eli Schwartz writes: > The current state of verify-sig support is a bit awkward. We rely on > validating distfiles against a known trusted keyring, but creating the > known trusted keyring is basically all manual verification. We somehow > decide an ascii armored key is good enough without any portage > assistance, then arrange to download it and trust it by Manifest hash. > How do we know when updating a key is actually safe? > > This eclass handles the problem in a manner inspired in part by pacman. > We require an eclass variable that lists all permitted PGP fingerprints, > and the eclass is responsible checking that list against the keys we > will install. It comes with a mechanism for computing SRC_URI for a > couple of well known locations, or you can append your own in the > ebuild. > > Key rotations, both expected and malicious, are easily detected by > checking the git log for changes to declared finterprints in a bump. The s/finterprints/fingerprints/ > former can be rationalized in the commit message. So can the latter, but > in most cases those will be rejected during peer review. > > Signed-off-by: Eli Schwartz > --- > eclass/sec-keys.eclass | 150 +++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 150 insertions(+) > create mode 100644 eclass/sec-keys.eclass > I'm actually pleasantly surprised by how elegant this is -- which is no reflection on you, just I sort of assumed the implementation would have to be a bit gnarlier (not sure why). One thing I really want to solve in general is key refreshes, given we have no "src_fetch", users can't opt in to key refreshes even with verify-sig.eclass unless network-sandbox is off, but that's a separate problem. mgorny's suggestion wrt src_test would allow us to even tinderbox needed refreshes which somewhat solves that problem too. I like it. > diff --git a/eclass/sec-keys.eclass b/eclass/sec-keys.eclass > new file mode 100644 > index 000000000000..95e1b4a92c0e > --- /dev/null > +++ b/eclass/sec-keys.eclass > @@ -0,0 +1,150 @@ > +# Copyright 2024 Gentoo Authors > +# Distributed under the terms of the GNU General Public License v2 > + > +# @ECLASS: sec-keys.eclass > +# @MAINTAINER: > +# Eli Schwartz > +# @AUTHOR: > +# Eli Schwartz > +# @SUPPORTED_EAPIS: 8 > +# @BLURB: Provides a uniform way of handling ebuilds which package PGP key material > +# @DESCRIPTION: > +# This eclass provides a streamlined approach to finding suitable source material > +# for OpenPGP keys used by the verify-sig eclass. Its primary purpose is to permit > +# developers to easily and securely package new sec-keys/* packages. The eclass > +# removes the risk of developers accidentally packaging malformed key material, or > +# neglecting to notice when PGP identities have changed. > +# > +# To use the eclass, define SEC_KEYS_VALIDPGPKEYS to contain the fingerprint of > +# the key and the short name of the key's owner. > +# > +# @EXAMPLE: > +# Example use: > +# > +# @CODE > +# SEC_KEYS_VALIDPGPKEYS=( > +# '4EC8A4DB7D2E01C00AF36C49E5C587B5E286C65A:jsmith:github' > +# ) Can you expand the example(s) here maybe with some comments in the array to help people see when it might be suitable to use e.g. none with a mix of github? > +# > +# inherit sec-keys > +# @CODE > + > +case ${EAPI} in > + 8) ;; > + *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; > +esac > + > +if [[ ! ${_SEC_KEYS_ECLASS} ]]; then > +_SEC_KEYS_ECLASS=1 > + > +inherit edo > + > +# @ECLASS_VARIABLE: SEC_KEYS_VALIDPGPKEYS > +# @PRE_INHERIT > +# @DEFAULT_UNSET > +# @DESCRIPTION: > +# Mapping of fingerprints, name, and optional location of PGP keys to include, > +# separated by colons. The allowed values for a location are: > +# > +# - github -- fetch key from github.com/${name}.pgp > +# > +# - openpgp -- fetch key by fingerprint from https://keys.openpgp.org > +# > +# - ubuntu -- fetch key by fingerprint from http://keyserver.ubuntu.com (the default) > +# > +# - none -- do not add to SRC_URI, the ebuild will provide a custom download location > +_sec_keys_set_globals() { > + if [[ ${SEC_KEYS_VALIDPGPKEYS[*]} ]]; then > + local key fingerprint name loc locations=() remote > + for key in "${SEC_KEYS_VALIDPGPKEYS[@]}"; do > + fingerprint=${key%%:*} > + name=${key#${fingerprint}:}; name=${name%%:*} > + IFS=: read -r -a locations <<<"${key##*:}" > + [[ ${locations[@]} ]] || locations=(ubuntu) > + for loc in "${locations[@]}"; do > + case ${loc} in > + github) remote="https://github.com/${name}.gpg";; > + openpgp) remote="https://keys.openpgp.org/vks/v1/by-fingerprint/${fingerprint}";; > + ubuntu) remote="https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x${fingerprint}";; > + # provided via manual SRC_URI > + none) continue;; > + *) die "${ECLASS}: unknown PGP key remote: ${loc}";; > + > + esac > + SRC_URI+=" > + ${remote} -> openpgp-keys-${name}-${loc}-${PV}.asc > + " > + done > + done > + fi > +} > +_sec_keys_set_globals > +unset -f _sec_keys_set_globals > + > +BDEPEND="app-crypt/gnupg" > +S=${WORKDIR} > + > +LICENSE="public-domain" > +SLOT="0" > + > + > +# @FUNCTION: sec-keys_src_compile > +# @DESCRIPTION: > +# Default src_compile override that imports all public keys into a keyring, > +# and validates that they are listed in SEC_KEYS_VALIDPGPKEYS. > +sec-keys_src_compile() { > + local -x GNUPGHOME=${WORKDIR}/gnupg > + mkdir -m700 -p "${GNUPGHOME}" || die Is there any value in using gemato's gpg-wrap for this function? > + > + pushd "${DISTDIR}" >/dev/null || die > + gpg --import ${A} || die > + popd >/dev/null || die > + > + local line imported_keys=() found=0 > + while IFS=: read -r -a line; do > + if [[ ${line[0]} = pub ]]; then > + # new key > + found=0 > + elif [[ ${found} = 0 && ${line[0]} = fpr ]]; then > + # primary fingerprint > + imported_keys+=("${line[9]}") > + found=1 > + fi > + done < <(gpg --batch --list-keys --keyid-format=long --with-colons || die) > + > + printf '%s\n' "${imported_keys[@]}" | sort > imported_keys.list || die > + printf '%s\n' "${SEC_KEYS_VALIDPGPKEYS[@]%%:*}" | sort > allowed_keys.list || die > + > + local extra_keys=($(comm -23 imported_keys.list allowed_keys.list || die)) > + local missing_keys=($(comm -13 imported_keys.list allowed_keys.list || die)) Any reason to not readarray this instead? > + > + if [[ ${#extra_keys[@]} != 0 ]]; then > + die "too many keys found. Suspicious keys: ${extra_keys[@]}" > + fi > + if [[ ${#missing_keys[@]} != 0 ]]; then > + die "too few keys found. Unavailable keys: ${missing_keys[@]}" > + fi > +} > + > +# @FUNCTION: sec-keys_src_install > +# @DESCRIPTION: > +# Default src_install override that minifies and exports all PGP public keys > +# into an ascii-armored keyfile installed to the standard /usr/share/openpgp-keys. > +sec-keys_src_install() { > + local -x GNUPGHOME=${WORKDIR}/gnupg > + local fingerprint > + local gpg_command=(gpg --no-permission-warning --export-options export-minimal) > + > + for fingerprint in "${SEC_KEYS_VALIDPGPKEYS[@]%%:*}"; do > + local uids=() > + mapfile -t uids < <("${gpg_command[@]}" --list-key --with-colons ${fingerprint} | awk -F: '/^uid/{print $10}' || die) > + edo "${gpg_command[@]}" "${uids[@]/#/--comment=}" --export --armor "${fingerprint}" >> ${PN#openpgp-keys-}.asc || die No need for the die here. > + done > + > + insinto /usr/share/openpgp-keys > + doins ${PN#openpgp-keys-}.asc > +} > + > +fi > + > +EXPORT_FUNCTIONS src_compile src_install