summaryrefslogtreecommitdiffstats
path: root/meta
diff options
context:
space:
mode:
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>2016-01-25 14:21:34 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2016-01-26 22:31:58 +0000
commitbb971577ab308caf7177d4bda290d1fe5ab842db (patch)
tree49c1811106a9b58717dcfd3c6fe4e4810341c1b3 /meta
parentaadb879e5b302e405e05443f56611c17868d10b6 (diff)
downloadpoky-bb971577ab308caf7177d4bda290d1fe5ab842db.tar.gz
meta/lib: new module for handling GPG signing
Add a new Python module (oe.gpg_sign) for handling GPG signing operations, i.e. currently package and package feed signing. The purpose is to be able to more easily support various signing backends and to be able to centralise signing functionality into one place (e.g. package signing and sstate signing). Currently, only local signing with gpg is implemented. [YOCTO #8755] (From OE-Core rev: 9b3dc1bd4b8336423a3f8f7db0ab5fa6fa0e7257) Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com> Signed-off-by: Ross Burton <ross.burton@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta')
-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