IPK signing for the gpg_sign module
v8 cover IPK signing for the gpg_sign module
Changes since v7:
* Removed the PACKAGE_FEED_GPG_PUBKEY variable
* Split the feed signing types into a separate commit and made it work also for rpm's besides ipk's
* Minor whitespace & exception handling cleanups
Ioan-Adrian Ratiu (6):
gpg_sign: add local ipk package signing functionality
gpg_sign: detach_sign: fix gpg > 2.1 STDIN file descriptor
gpg_sign: export_pubkey: add signature type support
signing-keys: create ipk package
package_manager: sign IPK package feeds
sign_package_feed: add feed signature type
meta/classes/package_ipk.bbclass | 5 +++ meta/classes/sign_ipk.bbclass | 52 +++++++++++++++++++++++++++++ meta/classes/sign_package_feed.bbclass | 12 ++++++- meta/lib/oe/gpg_sign.py | 60 ++++++++++++++++++++++++++-------- meta/lib/oe/package_manager.py | 22 +++++++++++-- meta/recipes-core/meta/signing-keys.bb | 15 ++++++++- 6 files changed, 147 insertions(+), 19 deletions(-) create mode 100644 meta/classes/sign_ipk.bbclass
Signature · 1 line
2.7.2
PATCH v8 1/6 gpg_sign: add local ipk package signing functionality
Implement ipk signing inside the sign_ipk bbclass using the gpg_sign module and configure signing similar to how rpm does it. sign_ipk uses gpg_sign's detach_sign because its functionality is identical to package feed signing.
IPK signing process is a bit different from rpm:
- Signatures are stored outside ipk files; opkg connects to a feed
server and downloads them to verify a package.
- Signatures are of two types (both supported by opkg): binary or
ascii armoured. By default we sign using ascii armoured.
- Public keys are stored on targets to verify ipks using the
opkg-keyrings recipe.
--- meta/classes/package_ipk.bbclass | 5 ++++ meta/classes/sign_ipk.bbclass | 52 ++++++++++++++++++++++++++++++++++++++++ meta/lib/oe/gpg_sign.py | 38 +++++++++++++++++++---------- 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 meta/classes/sign_ipk.bbclass
meta/classes/package_ipk.bbclass +5 -0
@@ -246,6 +246,11 @@ python do_package_ipk () { bb.utils.unlockfile(lf) raise bb.build.FuncFailed("opkg-build execution failed") + if d.getVar('IPK_SIGN_PACKAGES', True) == '1': + ipkver = "%s-%s" % (d.getVar('PKGV', True), d.getVar('PKGR', True)) + ipk_to_sign = "%s/%s_%s_%s.ipk" % (pkgoutdir, pkgname, ipkver, d.getVar('PACKAGE_ARCH', True)) + sign_ipk(d, ipk_to_sign) + cleanupcontrol(root) bb.utils.unlockfile(lf)
meta/classes/sign_ipk.bbclass new file +52 -0
52 lines not on this page · raw message
meta/lib/oe/gpg_sign.py +26 -12
@@ -50,6 +50,7 @@ class LocalSigner(object): bb.error('rpmsign failed: %s' % proc.before.strip()) raise bb.build.FuncFailed("Failed to sign RPM packages") + def detach_sign(self, input_file, keyid, passphrase_file, passphrase=None, armor=True): """Create a detached signature of a file""" import subprocess
@@ -58,22 +59,35 @@ class LocalSigner(object): raise Exception("You should use either passphrase_file of passphrase, not both") cmd = [self.gpg_bin, '--detach-sign', '--batch', '--no-tty', '--yes', - '-u', keyid] - if passphrase_file: - cmd += ['--passphrase-file', passphrase_file] - else: - cmd += ['--passphrase-fd', '0'] + '--passphrase-fd', '0', '-u', keyid] + if self.gpg_path: cmd += ['--homedir', self.gpg_path] if armor: cmd += ['--armor'] - cmd.append(input_file) - job = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - _, stderr = job.communicate(passphrase) - if job.returncode: - raise bb.build.FuncFailed("Failed to create signature for '%s': %s" % - (input_file, stderr)) + + cmd += [input_file] + + try: + if passphrase_file: + with open(passphrase_file) as fobj: + passphrase = fobj.readline(); + + job = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + (_, stderr) = job.communicate(passphrase) + + if job.returncode: + raise bb.build.FuncFailed("GPG exited with code %d: %s" % + (job.returncode, stderr)) + + except IOError as e: + bb.error("IO error (%s): %s" % (e.errno, e.strerror)) + raise Exception("Failed to sign '%s'" % input_file) + + except OSError as e: + bb.error("OS error (%s): %s" % (e.errno, e.strerror)) + raise Exception("Failed to sign '%s" % input_file) + def verify(self, sig_file): """Verify signature"""
Signature · 1 line
2.7.2
PATCH v8 2/6 gpg_sign: detach_sign: fix gpg > 2.1 STDIN file descriptor
In reply to Ioan-Adrian RatiuStarting from v2.1 passing passwords directly to gpg does not work anymore [1], instead a loopback interface must be used otherwise gpg >2.1 will error out with:
"gpg: signing failed: Inappropriate ioctl for device"
gpg <2.1 does not work with the new --pinentry-mode arg and gives an invalid option error, so we detect what is the running version of gpg and pass it accordingly.
[1] https://wiki.archlinux.org/index.php/GnuPG#Unattended_passphrase
--- meta/lib/oe/gpg_sign.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
meta/lib/oe/gpg_sign.py +16 -0
@@ -66,6 +66,13 @@ class LocalSigner(object): if armor: cmd += ['--armor'] + #gpg > 2.1 supports password pipes only through the loopback interface + #gpg < 2.1 errors out if given unknown parameters + dots = self.get_gpg_version().split('.') + assert len(dots) >= 2 + if int(dots[0]) >= 2 and int(dots[1]) >= 1: + cmd += ['--pinentry-mode', 'loopback'] + cmd += [input_file] try:
@@ -89,6 +96,15 @@ class LocalSigner(object): raise Exception("Failed to sign '%s" % input_file) + def get_gpg_version(self): + """Return the gpg version""" + import subprocess + try: + return subprocess.check_output((self.gpg_bin, "--version")).split()[2] + except subprocess.CalledProcessError as e: + raise bb.build.FuncFailed("Could not get gpg version: %s" % e) + + def verify(self, sig_file): """Verify signature""" cmd = self.gpg_bin + " --verify "
Signature · 1 line
2.7.2
PATCH v8 3/6 gpg_sign: export_pubkey: add signature type support
In reply to Ioan-Adrian RatiuAdd support for multiple types of signatures (binary or ascii) in export_pubkey(). There is no change in behaviour for the function, the previous implicit default is the new parameter "armor" default.
--- meta/lib/oe/gpg_sign.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
meta/lib/oe/gpg_sign.py +4 -2
@@ -12,12 +12,14 @@ class LocalSigner(object): self.gpg_path = d.getVar('GPG_PATH', True) self.rpm_bin = bb.utils.which(os.getenv('PATH'), "rpm") - def export_pubkey(self, output_file, keyid): + def export_pubkey(self, output_file, keyid, armor=True): """Export GPG public key to a file""" - cmd = '%s --batch --yes --export --armor -o %s ' % \ + cmd = '%s --batch --yes --export -o %s ' % \ (self.gpg_bin, output_file) if self.gpg_path: cmd += "--homedir %s " % self.gpg_path + if armor: + cmd += "--armor " cmd += keyid status, output = oe.utils.getstatusoutput(cmd) if status:
Signature · 1 line
2.7.2
PATCH v8 4/6 signing-keys: create ipk package
In reply to Ioan-Adrian RatiuStore the ascii armored pubkey generated using gpg_sign.export_pubkey() in its own package.
--- meta/recipes-core/meta/signing-keys.bb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
meta/recipes-core/meta/signing-keys.bb +14 -1
@@ -12,9 +12,10 @@ inherit allarch deploy EXCLUDE_FROM_WORLD = "1" INHIBIT_DEFAULT_DEPS = "1" -PACKAGES =+ "${PN}-rpm ${PN}-packagefeed" +PACKAGES =+ "${PN}-ipk ${PN}-rpm ${PN}-packagefeed" FILES_${PN}-rpm = "${sysconfdir}/pki/rpm-gpg" +FILES_${PN}-ipk = "${sysconfdir}/pki/ipk-gpg" FILES_${PN}-packagefeed = "${sysconfdir}/pki/packagefeed-gpg" python do_get_public_keys () {
@@ -26,6 +27,12 @@ python do_get_public_keys () { signer.export_pubkey(os.path.join(d.expand('${B}'), 'rpm-key'), d.getVar('RPM_GPG_NAME', True)) + if d.getVar("IPK_SIGN_PACKAGES", True): + # Export public key of the ipk signing key + signer = get_signer(d, d.getVar('IPK_GPG_BACKEND', True)) + signer.export_pubkey(os.path.join(d.expand('${B}'), 'ipk-key'), + d.getVar('IPK_GPG_NAME', True)) + if d.getVar('PACKAGE_FEED_SIGN', True) == '1': # Export public key of the feed signing key signer = get_signer(d, d.getVar('PACKAGE_FEED_GPG_BACKEND', True))
@@ -39,6 +46,9 @@ do_install () { if [ -f "${B}/rpm-key" ]; then install -D -m 0644 "${B}/rpm-key" "${D}${sysconfdir}/pki/rpm-gpg/RPM-GPG-KEY-${DISTRO_VERSION}" fi + if [ -f "${B}/ipk-key" ]; then + install -D -m 0644 "${B}/ipk-key" "${D}${sysconfdir}/pki/ipk-gpg/IPK-GPG-KEY-${DISTRO_VERSION}" + fi if [ -f "${B}/pf-key" ]; then install -D -m 0644 "${B}/pf-key" "${D}${sysconfdir}/pki/packagefeed-gpg/PACKAGEFEED-GPG-KEY-${DISTRO_VERSION}" fi
@@ -52,6 +62,9 @@ do_deploy () { if [ -f "${B}/rpm-key" ]; then install -D -m 0644 "${B}/rpm-key" "${DEPLOYDIR}/RPM-GPG-KEY-${DISTRO_VERSION}" fi + if [ -f "${B}/ipk-key" ]; then + install -D -m 0644 "${B}/ipk-key" "${DEPLOYDIR}/IPK-GPG-KEY-${DISTRO_VERSION}" + fi if [ -f "${B}/pf-key" ]; then install -D -m 0644 "${B}/pf-key" "${DEPLOYDIR}/PACKAGEFEED-GPG-KEY-${DISTRO_VERSION}" fi
Signature · 1 line
2.7.2
PATCH v8 5/6 package_manager: sign IPK package feeds
In reply to Ioan-Adrian RatiuCreate gpg signed ipk package feeds using the gpg backend if configured
--- meta/lib/oe/package_manager.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
meta/lib/oe/package_manager.py +12 -2
@@ -153,11 +153,16 @@ class OpkgIndexer(Indexer): "MULTILIB_ARCHS"] opkg_index_cmd = bb.utils.which(os.getenv('PATH'), "opkg-make-index") + if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1': + signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True)) + else: + signer = None if not os.path.exists(os.path.join(self.deploy_dir, "Packages")): open(os.path.join(self.deploy_dir, "Packages"), "w").close() index_cmds = [] + index_sign_files = [] for arch_var in arch_vars: archs = self.d.getVar(arch_var, True) if archs is None:
@@ -176,6 +181,8 @@ class OpkgIndexer(Indexer): index_cmds.append('%s -r %s -p %s -m %s' % (opkg_index_cmd, pkgs_file, pkgs_file, pkgs_dir)) + index_sign_files.append(pkgs_file) + if len(index_cmds) == 0: bb.note("There are no packages in %s!" % self.deploy_dir) return
@@ -183,9 +190,12 @@ class OpkgIndexer(Indexer): result = oe.utils.multiprocess_exec(index_cmds, create_index) if result: bb.fatal('%s' % ('\n'.join(result))) - if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1': - raise NotImplementedError('Package feed signing not implementd for ipk') + if signer: + for f in index_sign_files: + signer.detach_sign(f, + self.d.getVar('PACKAGE_FEED_GPG_NAME', True), + self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) class DpkgIndexer(Indexer):
Signature · 1 line
2.7.2
PATCH v8 6/6 sign_package_feed: add feed signature type
In reply to Ioan-Adrian RatiuSigning package feeds will default to ascii armored signatures (ASC) the other option being binary (BIN). This is for both rpm and ipk backends.
--- meta/classes/sign_package_feed.bbclass | 12 +++++++++++- meta/lib/oe/package_manager.py | 10 ++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-)
meta/classes/sign_package_feed.bbclass +11 -1
@@ -10,6 +10,12 @@ # Optional variable for specifying the backend to use for signing. # Currently the only available option is 'local', i.e. local signing # on the build host. +# PACKAGE_FEED_GPG_SIGNATURE_TYPE +# Optional variable for specifying the type of gpg signature, can be: +# 1. Ascii armored (ASC), default if not set +# 2. Binary (BIN) +# This variable is only available for IPK feeds. It is ignored on +# other packaging backends. # GPG_BIN # Optional variable for specifying the gpg binary/wrapper to use for # signing.
@@ -20,13 +26,17 @@ inherit sanity PACKAGE_FEED_SIGN = '1' PACKAGE_FEED_GPG_BACKEND ?= 'local' - +PACKAGE_FEED_GPG_SIGNATURE_TYPE ?= 'ASC' python () { # Check sanity of configuration for var in ('PACKAGE_FEED_GPG_NAME', 'PACKAGE_FEED_GPG_PASSPHRASE_FILE'): if not d.getVar(var, True): raise_sanity_error("You need to define %s in the config" % var, d) + + sigtype = d.getVar("PACKAGE_FEED_GPG_SIGNATURE_TYPE", True) + if sigtype.upper() != "ASC" and sigtype.upper() != "BIN": + raise_sanity_error("Bad value for PACKAGE_FEED_GPG_SIGNATURE_TYPE (%s), use either ASC or BIN" % sigtype) } do_package_index[depends] += "signing-keys:do_deploy"
meta/lib/oe/package_manager.py +8 -2
@@ -141,9 +141,12 @@ class RpmIndexer(Indexer): # Sign repomd if signer: for repomd in repomd_files: + feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE', True) + is_ascii_sig = (feed_sig_type.upper() != "BIN") signer.detach_sign(repomd, self.d.getVar('PACKAGE_FEED_GPG_NAME', True), - self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) + self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True), + armor=is_ascii_sig) class OpkgIndexer(Indexer):
@@ -192,10 +195,13 @@ class OpkgIndexer(Indexer): bb.fatal('%s' % ('\n'.join(result))) if signer: + feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE', True) + is_ascii_sig = (feed_sig_type.upper() != "BIN") for f in index_sign_files: signer.detach_sign(f, self.d.getVar('PACKAGE_FEED_GPG_NAME', True), - self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) + self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True), + armor=is_ascii_sig) class DpkgIndexer(Indexer):
Signature · 1 line
2.7.2