summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/classes/sign_package_feed.bbclass6
-rw-r--r--meta/classes/sign_rpm.bbclass47
-rw-r--r--meta/lib/oe/gpg_sign.py76
-rw-r--r--meta/lib/oe/package_manager.py31
-rw-r--r--meta/recipes-core/meta/signing-keys.bb26
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 @@
15inherit sanity 19inherit sanity
16 20
17PACKAGE_FEED_SIGN = '1' 21PACKAGE_FEED_SIGN = '1'
22PACKAGE_FEED_GPG_BACKEND ?= 'local'
23
18 24
19python () { 25python () {
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 @@
14inherit sanity 18inherit sanity
15 19
16RPM_SIGN_PACKAGES='1' 20RPM_SIGN_PACKAGES='1'
21RPM_GPG_BACKEND ?= 'local'
17 22
18 23
19python () { 24python () {
@@ -27,47 +32,17 @@ python () {
27 'RPM-GPG-PUBKEY')) 32 'RPM-GPG-PUBKEY'))
28} 33}
29 34
30
31def 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
59python sign_rpm () { 35python 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
73do_package_index[depends] += "signing-keys:do_export_public_keys" 48do_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"""
2import os
3
4import bb
5import oe.utils
6
7class 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
69def 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
9import tempfile 9import tempfile
10import oe.utils 10import oe.utils
11import string 11import string
12from 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
14def create_index(arg): 15def 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
21EXCLUDE_FROM_WORLD = "1" 21EXCLUDE_FROM_WORLD = "1"
22 22
23def 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
34python do_export_public_keys () { 24python 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}
45addtask do_export_public_keys before do_build 43addtask do_export_public_keys before do_build