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>
This commit is contained in:
parent
aadb879e5b
commit
bb971577ab
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue