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 @@ | |||
| 6 | # Path to a file containing the passphrase of the signing key. | 6 | # Path to a file containing the passphrase of the signing key. |
| 7 | # PACKAGE_FEED_GPG_NAME | 7 | # PACKAGE_FEED_GPG_NAME |
| 8 | # Name of the key to sign with. May be key id or key name. | 8 | # Name of the key to sign with. May be key id or key name. |
| 9 | # PACKAGE_FEED_GPG_BACKEND | ||
| 10 | # Optional variable for specifying the backend to use for signing. | ||
| 11 | # Currently the only available option is 'local', i.e. local signing | ||
| 12 | # on the build host. | ||
| 9 | # GPG_BIN | 13 | # GPG_BIN |
| 10 | # Optional variable for specifying the gpg binary/wrapper to use for | 14 | # Optional variable for specifying the gpg binary/wrapper to use for |
| 11 | # signing. | 15 | # signing. |
| @@ -15,6 +19,8 @@ | |||
| 15 | inherit sanity | 19 | inherit sanity |
| 16 | 20 | ||
| 17 | PACKAGE_FEED_SIGN = '1' | 21 | PACKAGE_FEED_SIGN = '1' |
| 22 | PACKAGE_FEED_GPG_BACKEND ?= 'local' | ||
| 23 | |||
| 18 | 24 | ||
| 19 | python () { | 25 | python () { |
| 20 | # Check sanity of configuration | 26 | # 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 @@ | |||
| 5 | # Path to a file containing the passphrase of the signing key. | 5 | # Path to a file containing the passphrase of the signing key. |
| 6 | # RPM_GPG_NAME | 6 | # RPM_GPG_NAME |
| 7 | # Name of the key to sign with. May be key id or key name. | 7 | # Name of the key to sign with. May be key id or key name. |
| 8 | # RPM_GPG_BACKEND | ||
| 9 | # Optional variable for specifying the backend to use for signing. | ||
| 10 | # Currently the only available option is 'local', i.e. local signing | ||
| 11 | # on the build host. | ||
| 8 | # GPG_BIN | 12 | # GPG_BIN |
| 9 | # Optional variable for specifying the gpg binary/wrapper to use for | 13 | # Optional variable for specifying the gpg binary/wrapper to use for |
| 10 | # signing. | 14 | # signing. |
| @@ -14,6 +18,7 @@ | |||
| 14 | inherit sanity | 18 | inherit sanity |
| 15 | 19 | ||
| 16 | RPM_SIGN_PACKAGES='1' | 20 | RPM_SIGN_PACKAGES='1' |
| 21 | RPM_GPG_BACKEND ?= 'local' | ||
| 17 | 22 | ||
| 18 | 23 | ||
| 19 | python () { | 24 | python () { |
| @@ -27,47 +32,17 @@ python () { | |||
| 27 | 'RPM-GPG-PUBKEY')) | 32 | 'RPM-GPG-PUBKEY')) |
| 28 | } | 33 | } |
| 29 | 34 | ||
| 30 | |||
| 31 | def rpmsign_wrapper(d, files, passphrase, gpg_name=None): | ||
| 32 | import pexpect | ||
| 33 | |||
| 34 | # Find the correct rpm binary | ||
| 35 | rpm_bin_path = d.getVar('STAGING_BINDIR_NATIVE', True) + '/rpm' | ||
| 36 | cmd = rpm_bin_path + " --addsign --define '_gpg_name %s' " % gpg_name | ||
| 37 | if d.getVar('GPG_BIN', True): | ||
| 38 | cmd += "--define '%%__gpg %s' " % d.getVar('GPG_BIN', True) | ||
| 39 | if d.getVar('GPG_PATH', True): | ||
| 40 | cmd += "--define '_gpg_path %s' " % d.getVar('GPG_PATH', True) | ||
| 41 | cmd += ' '.join(files) | ||
| 42 | |||
| 43 | # Need to use pexpect for feeding the passphrase | ||
| 44 | proc = pexpect.spawn(cmd) | ||
| 45 | try: | ||
| 46 | proc.expect_exact('Enter pass phrase:', timeout=15) | ||
| 47 | proc.sendline(passphrase) | ||
| 48 | proc.expect(pexpect.EOF, timeout=900) | ||
| 49 | proc.close() | ||
| 50 | except pexpect.TIMEOUT as err: | ||
| 51 | bb.warn('rpmsign timeout: %s' % err) | ||
| 52 | proc.terminate() | ||
| 53 | else: | ||
| 54 | if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status): | ||
| 55 | bb.warn('rpmsign failed: %s' % proc.before.strip()) | ||
| 56 | return proc.exitstatus | ||
| 57 | |||
| 58 | |||
| 59 | python sign_rpm () { | 35 | python sign_rpm () { |
| 60 | import glob | 36 | import glob |
| 37 | from oe.gpg_sign import get_signer | ||
| 61 | 38 | ||
| 62 | with open(d.getVar("RPM_GPG_PASSPHRASE_FILE", True)) as fobj: | 39 | signer = get_signer(d, |
| 63 | rpm_gpg_passphrase = fobj.readlines()[0].rstrip('\n') | 40 | d.getVar('RPM_GPG_BACKEND', True), |
| 64 | 41 | d.getVar('RPM_GPG_NAME', True), | |
| 65 | rpm_gpg_name = (d.getVar("RPM_GPG_NAME", True) or "") | 42 | d.getVar('RPM_GPG_PASSPHRASE_FILE', True)) |
| 66 | |||
| 67 | rpms = glob.glob(d.getVar('RPM_PKGWRITEDIR', True) + '/*') | 43 | rpms = glob.glob(d.getVar('RPM_PKGWRITEDIR', True) + '/*') |
| 68 | 44 | ||
| 69 | if rpmsign_wrapper(d, rpms, rpm_gpg_passphrase, rpm_gpg_name) != 0: | 45 | signer.sign_rpms(rpms) |
| 70 | raise bb.build.FuncFailed("RPM signing failed") | ||
| 71 | } | 46 | } |
| 72 | 47 | ||
| 73 | do_package_index[depends] += "signing-keys:do_export_public_keys" | 48 | 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 @@ | |||
| 1 | """Helper module for GPG signing""" | ||
| 2 | import os | ||
| 3 | |||
| 4 | import bb | ||
| 5 | import oe.utils | ||
| 6 | |||
| 7 | class LocalSigner(object): | ||
| 8 | """Class for handling local (on the build host) signing""" | ||
| 9 | def __init__(self, d, keyid, passphrase_file): | ||
| 10 | self.keyid = keyid | ||
| 11 | self.passphrase_file = passphrase_file | ||
| 12 | self.gpg_bin = d.getVar('GPG_BIN', True) or \ | ||
| 13 | bb.utils.which(os.getenv('PATH'), 'gpg') | ||
| 14 | self.gpg_path = d.getVar('GPG_PATH', True) | ||
| 15 | self.rpm_bin = bb.utils.which(os.getenv('PATH'), "rpm") | ||
| 16 | |||
| 17 | def export_pubkey(self, output_file): | ||
| 18 | """Export GPG public key to a file""" | ||
| 19 | cmd = '%s --batch --yes --export --armor -o %s ' % \ | ||
| 20 | (self.gpg_bin, output_file) | ||
| 21 | if self.gpg_path: | ||
| 22 | cmd += "--homedir %s " % self.gpg_path | ||
| 23 | cmd += self.keyid | ||
| 24 | status, output = oe.utils.getstatusoutput(cmd) | ||
| 25 | if status: | ||
| 26 | raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % | ||
| 27 | (self.keyid, output)) | ||
| 28 | |||
| 29 | def sign_rpms(self, files): | ||
| 30 | """Sign RPM files""" | ||
| 31 | import pexpect | ||
| 32 | |||
| 33 | cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % self.keyid | ||
| 34 | if self.gpg_bin: | ||
| 35 | cmd += "--define '%%__gpg %s' " % self.gpg_bin | ||
| 36 | if self.gpg_path: | ||
| 37 | cmd += "--define '_gpg_path %s' " % self.gpg_path | ||
| 38 | cmd += ' '.join(files) | ||
| 39 | |||
| 40 | # Need to use pexpect for feeding the passphrase | ||
| 41 | proc = pexpect.spawn(cmd) | ||
| 42 | try: | ||
| 43 | proc.expect_exact('Enter pass phrase:', timeout=15) | ||
| 44 | with open(self.passphrase_file) as fobj: | ||
| 45 | proc.sendline(fobj.readline().rstrip('\n')) | ||
| 46 | proc.expect(pexpect.EOF, timeout=900) | ||
| 47 | proc.close() | ||
| 48 | except pexpect.TIMEOUT as err: | ||
| 49 | bb.error('rpmsign timeout: %s' % err) | ||
| 50 | proc.terminate() | ||
| 51 | if os.WEXITSTATUS(proc.status) or not os.WIFEXITED(proc.status): | ||
| 52 | bb.error('rpmsign failed: %s' % proc.before.strip()) | ||
| 53 | raise bb.build.FuncFailed("Failed to sign RPM packages") | ||
| 54 | |||
| 55 | def detach_sign(self, input_file): | ||
| 56 | """Create a detached signature of a file""" | ||
| 57 | cmd = "%s --detach-sign --armor --batch --no-tty --yes " \ | ||
| 58 | "--passphrase-file '%s' -u '%s' " % \ | ||
| 59 | (self.gpg_bin, self.passphrase_file, self.keyid) | ||
| 60 | if self.gpg_path: | ||
| 61 | gpg_cmd += "--homedir %s " % self.gpg_path | ||
| 62 | cmd += input_file | ||
| 63 | status, output = oe.utils.getstatusoutput(cmd) | ||
| 64 | if status: | ||
| 65 | raise bb.build.FuncFailed("Failed to create signature for '%s': %s" % | ||
| 66 | (input_file, output)) | ||
| 67 | |||
| 68 | |||
| 69 | def get_signer(d, backend, keyid, passphrase_file): | ||
| 70 | """Get signer object for the specified backend""" | ||
| 71 | # Use local signing by default | ||
| 72 | if backend == 'local': | ||
| 73 | return LocalSigner(d, keyid, passphrase_file) | ||
| 74 | else: | ||
| 75 | bb.fatal("Unsupported signing backend '%s'" % backend) | ||
| 76 | |||
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 | |||
| 9 | import tempfile | 9 | import tempfile |
| 10 | import oe.utils | 10 | import oe.utils |
| 11 | import string | 11 | import string |
| 12 | from oe.gpg_sign import get_signer | ||
| 12 | 13 | ||
| 13 | # this can be used by all PM backends to create the index files in parallel | 14 | # this can be used by all PM backends to create the index files in parallel |
| 14 | def create_index(arg): | 15 | def create_index(arg): |
| @@ -109,16 +110,14 @@ class RpmIndexer(Indexer): | |||
| 109 | 110 | ||
| 110 | rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo") | 111 | rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo") |
| 111 | if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1': | 112 | if self.d.getVar('PACKAGE_FEED_SIGN', True) == '1': |
| 112 | pkgfeed_gpg_name = self.d.getVar('PACKAGE_FEED_GPG_NAME', True) | 113 | signer = get_signer(self.d, |
| 113 | pkgfeed_gpg_pass = self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True) | 114 | self.d.getVar('PACKAGE_FEED_GPG_BACKEND', True), |
| 115 | self.d.getVar('PACKAGE_FEED_GPG_NAME', True), | ||
| 116 | self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) | ||
| 114 | else: | 117 | else: |
| 115 | pkgfeed_gpg_name = None | 118 | signer = None |
| 116 | pkgfeed_gpg_pass = None | ||
| 117 | gpg_bin = self.d.getVar('GPG_BIN', True) or \ | ||
| 118 | bb.utils.which(os.getenv('PATH'), "gpg") | ||
| 119 | |||
| 120 | index_cmds = [] | 119 | index_cmds = [] |
| 121 | repo_sign_cmds = [] | 120 | repomd_files = [] |
| 122 | rpm_dirs_found = False | 121 | rpm_dirs_found = False |
| 123 | for arch in archs: | 122 | for arch in archs: |
| 124 | dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch) | 123 | dbpath = os.path.join(self.d.getVar('WORKDIR', True), 'rpmdb', arch) |
| @@ -130,15 +129,7 @@ class RpmIndexer(Indexer): | |||
| 130 | 129 | ||
| 131 | index_cmds.append("%s --dbpath %s --update -q %s" % \ | 130 | index_cmds.append("%s --dbpath %s --update -q %s" % \ |
| 132 | (rpm_createrepo, dbpath, arch_dir)) | 131 | (rpm_createrepo, dbpath, arch_dir)) |
| 133 | if pkgfeed_gpg_name: | 132 | repomd_files.append(os.path.join(arch_dir, 'repodata', 'repomd.xml')) |
| 134 | repomd_file = os.path.join(arch_dir, 'repodata', 'repomd.xml') | ||
| 135 | gpg_cmd = "%s --detach-sign --armor --batch --no-tty --yes " \ | ||
| 136 | "--passphrase-file '%s' -u '%s' " % \ | ||
| 137 | (gpg_bin, pkgfeed_gpg_pass, pkgfeed_gpg_name) | ||
| 138 | if self.d.getVar('GPG_PATH', True): | ||
| 139 | gpg_cmd += "--homedir %s " % self.d.getVar('GPG_PATH', True) | ||
| 140 | gpg_cmd += repomd_file | ||
| 141 | repo_sign_cmds.append(gpg_cmd) | ||
| 142 | 133 | ||
| 143 | rpm_dirs_found = True | 134 | rpm_dirs_found = True |
| 144 | 135 | ||
| @@ -151,9 +142,9 @@ class RpmIndexer(Indexer): | |||
| 151 | if result: | 142 | if result: |
| 152 | bb.fatal('%s' % ('\n'.join(result))) | 143 | bb.fatal('%s' % ('\n'.join(result))) |
| 153 | # Sign repomd | 144 | # Sign repomd |
| 154 | result = oe.utils.multiprocess_exec(repo_sign_cmds, create_index) | 145 | if signer: |
| 155 | if result: | 146 | for repomd in repomd_files: |
| 156 | bb.fatal('%s' % ('\n'.join(result))) | 147 | signer.detach_sign(repomd) |
| 157 | # Copy pubkey(s) to repo | 148 | # Copy pubkey(s) to repo |
| 158 | distro_version = self.d.getVar('DISTRO_VERSION', True) or "oe.0" | 149 | distro_version = self.d.getVar('DISTRO_VERSION', True) or "oe.0" |
| 159 | if self.d.getVar('RPM_SIGN_PACKAGES', True) == '1': | 150 | 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" | |||
| 20 | 20 | ||
| 21 | EXCLUDE_FROM_WORLD = "1" | 21 | EXCLUDE_FROM_WORLD = "1" |
| 22 | 22 | ||
| 23 | def export_gpg_pubkey(d, keyid, path): | ||
| 24 | import bb | ||
| 25 | gpg_bin = d.getVar('GPG_BIN', True) or \ | ||
| 26 | bb.utils.which(os.getenv('PATH'), "gpg") | ||
| 27 | cmd = '%s --batch --yes --export --armor -o %s %s' % \ | ||
| 28 | (gpg_bin, path, keyid) | ||
| 29 | status, output = oe.utils.getstatusoutput(cmd) | ||
| 30 | if status: | ||
| 31 | raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % | ||
| 32 | (keyid, output)) | ||
| 33 | 23 | ||
| 34 | python do_export_public_keys () { | 24 | python do_export_public_keys () { |
| 25 | from oe.gpg_sign import get_signer | ||
| 26 | |||
| 35 | if d.getVar("RPM_SIGN_PACKAGES", True): | 27 | if d.getVar("RPM_SIGN_PACKAGES", True): |
| 36 | # Export public key of the rpm signing key | 28 | # Export public key of the rpm signing key |
| 37 | export_gpg_pubkey(d, d.getVar("RPM_GPG_NAME", True), | 29 | signer = get_signer(d, |
| 38 | d.getVar('RPM_GPG_PUBKEY', True)) | 30 | d.getVar('RPM_GPG_BACKEND', True), |
| 31 | d.getVar('RPM_GPG_NAME', True), | ||
| 32 | d.getVar('RPM_GPG_PASSPHRASE_FILE', True)) | ||
| 33 | signer.export_pubkey(d.getVar('RPM_GPG_PUBKEY', True)) | ||
| 39 | 34 | ||
| 40 | if d.getVar('PACKAGE_FEED_SIGN', True) == '1': | 35 | if d.getVar('PACKAGE_FEED_SIGN', True) == '1': |
| 41 | # Export public key of the feed signing key | 36 | # Export public key of the feed signing key |
| 42 | export_gpg_pubkey(d, d.getVar("PACKAGE_FEED_GPG_NAME", True), | 37 | signer = get_signer(d, |
| 43 | d.getVar('PACKAGE_FEED_GPG_PUBKEY', True)) | 38 | d.getVar('PACKAGE_FEED_GPG_BACKEND', True), |
| 39 | d.getVar('PACKAGE_FEED_GPG_NAME', True), | ||
| 40 | d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE', True)) | ||
| 41 | signer.export_pubkey(d.getVar('PACKAGE_FEED_GPG_PUBKEY', True)) | ||
| 44 | } | 42 | } |
| 45 | addtask do_export_public_keys before do_build | 43 | addtask do_export_public_keys before do_build |
