diff options
-rw-r--r-- | meta/classes/sign_package_feed.bbclass | 6 | ||||
-rw-r--r-- | meta/classes/sign_rpm.bbclass | 47 | ||||
-rw-r--r-- | meta/lib/oe/gpg_sign.py | 76 | ||||
-rw-r--r-- | meta/lib/oe/package_manager.py | 31 | ||||
-rw-r--r-- | meta/recipes-core/meta/signing-keys.bb | 26 |
5 files changed, 116 insertions, 70 deletions
diff --git a/meta/classes/sign_package_feed.bbclass b/meta/classes/sign_package_feed.bbclass index d89bc0b195..d5df8afb9f 100644 --- a/meta/classes/sign_package_feed.bbclass +++ b/meta/classes/sign_package_feed.bbclass @@ -6,6 +6,10 @@ # Path to a file containing the passphrase of the signing key. # PACKAGE_FEED_GPG_NAME # Name of the key to sign with. May be key id or key name. +# PACKAGE_FEED_GPG_BACKEND +# 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. # GPG_BIN # Optional variable for specifying the gpg binary/wrapper to use for # signing. @@ -15,6 +19,8 @@ inherit sanity PACKAGE_FEED_SIGN = '1' +PACKAGE_FEED_GPG_BACKEND ?= 'local' + python () { # Check sanity of configuration diff --git a/meta/classes/sign_rpm.bbclass b/meta/classes/sign_rpm.bbclass index 7906b6413b..8bcabeec91 100644 --- a/meta/classes/sign_rpm.bbclass +++ b/meta/classes/sign_rpm.bbclass @@ -5,6 +5,10 @@ # Path to a file containing the passphrase of the signing key. # RPM_GPG_NAME # Name of the key to sign with. May be key id or key name. +# RPM_GPG_BACKEND +# 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. # GPG_BIN # Optional variable for specifying the gpg binary/wrapper to use for # signing. @@ -14,6 +18,7 @@ inherit sanity RPM_SIGN_PACKAGES='1' +RPM_GPG_BACKEND ?= 'local' python () { @@ -27,47 +32,17 @@ python () { 'RPM-GPG-PUBKEY')) } - -def rpmsign_wrapper(d, files, passphrase, gpg_name=None): - import pexpect - - # Find the correct rpm binary - rpm_bin_path = d.getVar('STAGING_BINDIR_NATIVE', True) + '/rpm' - cmd = rpm_bin_path + " --addsign --define '_gpg_name %s' " % gpg_name - if d.getVar('GPG_BIN', True): - cmd += "--define '%%__gpg %s' " % d.getVar('GPG_BIN', True) - if d.getVar('GPG_PATH', True): - cmd += "--define '_gpg_path %s' " % d.getVar('GPG_PATH', True) - cmd += ' '.join(files) - - # Need to use pexpect for feeding the passphrase - proc = pexpect.spawn(cmd) - try: - proc.expect_exact('Enter pass phrase:', timeout=15) - proc.sendline(passphrase) - proc.expect(pexpect.EOF, timeout=900) - proc.close() - except pexpect.TIMEOUT as err: - bb.warn('rpmsign timeout: %s' % err) - proc.terminate() - else: - if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status): - bb.warn('rpmsign failed: %s' % proc.before.strip()) - return proc.exitstatus - - python sign_rpm () { import glob + from oe.gpg_sign import get_signer - with open(d.getVar("RPM_GPG_PASSPHRASE_FILE", True)) as fobj: - rpm_gpg_passphrase = fobj.readlines()[0].rstrip('\n') - - rpm_gpg_name = (d.getVar("RPM_GPG_NAME", True) or "") - + signer = get_signer(d, + d.getVar('RPM_GPG_BACKEND', True), + d.getVar('RPM_GPG_NAME', True), + d.getVar('RPM_GPG_PASSPHRASE_FILE', True)) rpms = glob.glob(d.getVar('RPM_PKGWRITEDIR', True) + '/*') - if rpmsign_wrapper(d, rpms, rpm_gpg_passphrase, rpm_gpg_name) != 0: - raise bb.build.FuncFailed("RPM signing failed") + signer.sign_rpms(rpms) } do_package_index[depends] += "signing-keys:do_export_public_keys" diff --git a/meta/lib/oe/gpg_sign.py b/meta/lib/oe/gpg_sign.py new file mode 100644 index 0000000000..55abad8ffc --- /dev/null +++ b/meta/lib/oe/gpg_sign.py @@ -0,0 +1,76 @@ +"""Helper module for GPG signing""" +import os + +import bb +import oe.utils + +class LocalSigner(object): + """Class for handling local (on the build host) signing""" + def __init__(self, d, keyid, passphrase_file): + self.keyid = keyid + self.passphrase_file = passphrase_file + self.gpg_bin = d.getVar('GPG_BIN', True) or \ + bb.utils.which(os.getenv('PATH'), 'gpg') + self.gpg_path = d.getVar('GPG_PATH', True) + self.rpm_bin = bb.utils.which(os.getenv('PATH'), "rpm") + + def export_pubkey(self, output_file): + """Export GPG public key to a file""" + cmd = '%s --batch --yes --export --armor -o %s ' % \ + (self.gpg_bin, output_file) + if self.gpg_path: + cmd += "--homedir %s " % self.gpg_path + cmd += self.keyid + status, output = oe.utils.getstatusoutput(cmd) + if status: + raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % + (self.keyid, output)) + + def sign_rpms(self, files): + """Sign RPM files""" + import pexpect + + cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % self.keyid + if self.gpg_bin: + cmd += "--define '%%__gpg %s' " % self.gpg_bin + if self.gpg_path: + cmd += "--define '_gpg_path %s' " % self.gpg_path + cmd += ' '.join(files) + + # Need to use pexpect for feeding the passphrase + proc = pexpect.spawn(cmd) + try: + proc.expect_exact('Enter pass phrase:', timeout=15) + with open(self.passphrase_file) as fobj: + proc.sendline(fobj.readline().rstrip('\n')) + proc.expect(pexpect.EOF, timeout=900) + proc.close() + except pexpect.TIMEOUT as err: + bb.error('rpmsign timeout: %s' % err) + proc.terminate() + if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status): + bb.error('rpmsign failed: %s' % proc.before.strip()) + raise bb.build.FuncFailed("Failed to sign RPM packages") + + def detach_sign(self, input_file): + """Create a detached signature of a file""" + cmd = "%s --detach-sign --armor --batch --no-tty --yes " \ + "--passphrase-file '%s' -u '%s' " % \ + (self.gpg_bin, self.passphrase_file, self.keyid) + if self.gpg_path: + gpg_cmd += "--homedir %s " % self.gpg_path + cmd += input_file + status, output = oe.utils.getstatusoutput(cmd) + if status: + raise bb.build.FuncFailed("Failed to create signature for '%s': %s" % + (input_file, output)) + + +def get_signer(d, backend, keyid, passphrase_file): + """Get signer object for the specified backend""" + # Use local signing by default + if backend == 'local': + return LocalSigner(d, keyid, passphrase_file) + else: + bb.fatal("Unsupported signing backend '%s'" % backend) + diff --git a/meta/lib/oe/package_manager.py b/meta/lib/oe/package_manager.py index 5b87f45127..3f9e4e3b60 100644 --- a/meta/lib/oe/package_manager.py +++ b/meta/lib/oe/package_manager.py @@ -9,6 +9,7 @@ import bb import tempfile import oe.utils import string +from oe.gpg_sign import get_signer # this can be used by all PM backends to create the index files in parallel def create_index(arg): @@ -109,16 +110,14 @@ class RpmIndexer(Indexer): rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo") if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1': - pkgfeed_gpg_name = self.d.getVar('PACKAGE_FEED_GPG_NAME', True) - pkgfeed_gpg_pass = self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True) + signer = get_signer(self.d, + self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True), + self.d.getVar('PACKAGE_FEED_GPG_NAME', True), + self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) else: - pkgfeed_gpg_name = None - pkgfeed_gpg_pass = None - gpg_bin = self.d.getVar('GPG_BIN', True) or \ - bb.utils.which(os.getenv('PATH'), "gpg") - + signer = None index_cmds = [] - repo_sign_cmds = [] + repomd_files = [] rpm_dirs_found = False for arch in archs: dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch) @@ -130,15 +129,7 @@ class RpmIndexer(Indexer): index_cmds.append("%s --dbpath %s --update -q %s" % \ (rpm_createrepo, dbpath, arch_dir)) - if pkgfeed_gpg_name: - repomd_file = os.path.join(arch_dir, 'repodata', 'repomd.xml') - gpg_cmd = "%s --detach-sign --armor --batch --no-tty --yes " \ - "--passphrase-file '%s' -u '%s' " % \ - (gpg_bin, pkgfeed_gpg_pass, pkgfeed_gpg_name) - if self.d.getVar('GPG_PATH', True): - gpg_cmd += "--homedir %s " % self.d.getVar('GPG_PATH', True) - gpg_cmd += repomd_file - repo_sign_cmds.append(gpg_cmd) + repomd_files.append(os.path.join(arch_dir, 'repodata', 'repomd.xml')) rpm_dirs_found = True @@ -151,9 +142,9 @@ class RpmIndexer(Indexer): if result: bb.fatal('%s' % ('\n'.join(result))) # Sign repomd - result = oe.utils.multiprocess_exec(repo_sign_cmds, create_index) - if result: - bb.fatal('%s' % ('\n'.join(result))) + if signer: + for repomd in repomd_files: + signer.detach_sign(repomd) # Copy pubkey(s) to repo distro_version = self.d.getVar('DISTRO_VERSION', True) or "oe.0" if self.d.getVar('RPM_SIGN_PACKAGES', True) == '1': diff --git a/meta/recipes-core/meta/signing-keys.bb b/meta/recipes-core/meta/signing-keys.bb index cc401f3b6c..d7aa79d49f 100644 --- a/meta/recipes-core/meta/signing-keys.bb +++ b/meta/recipes-core/meta/signing-keys.bb @@ -20,26 +20,24 @@ do_populate_sysroot[noexec] = "1" EXCLUDE_FROM_WORLD = "1" -def export_gpg_pubkey(d, keyid, path): - import bb - gpg_bin = d.getVar('GPG_BIN', True) or \ - bb.utils.which(os.getenv('PATH'), "gpg") - cmd = '%s --batch --yes --export --armor -o %s %s' % \ - (gpg_bin, path, keyid) - status, output = oe.utils.getstatusoutput(cmd) - if status: - raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % - (keyid, output)) python do_export_public_keys () { + from oe.gpg_sign import get_signer + if d.getVar("RPM_SIGN_PACKAGES", True): # Export public key of the rpm signing key - export_gpg_pubkey(d, d.getVar("RPM_GPG_NAME", True), - d.getVar('RPM_GPG_PUBKEY', True)) + signer = get_signer(d, + d.getVar('RPM_GPG_BACKEND', True), + d.getVar('RPM_GPG_NAME', True), + d.getVar('RPM_GPG_PASSPHRASE_FILE', True)) + signer.export_pubkey(d.getVar('RPM_GPG_PUBKEY', True)) if d.getVar('PACKAGE_FEED_SIGN', True) == '1': # Export public key of the feed signing key - export_gpg_pubkey(d, d.getVar("PACKAGE_FEED_GPG_NAME", True), - d.getVar('PACKAGE_FEED_GPG_PUBKEY', True)) + signer = get_signer(d, + d.getVar('PACKAGE_FEED_GPG_BACKEND', True), + d.getVar('PACKAGE_FEED_GPG_NAME', True), + d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) + signer.export_pubkey(d.getVar('PACKAGE_FEED_GPG_PUBKEY', True)) } addtask do_export_public_keys before do_build |