Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 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): |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 10 | self.gpg_bin = d.getVar('GPG_BIN') or \ |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 11 | bb.utils.which(os.getenv('PATH'), 'gpg') |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 12 | self.gpg_path = d.getVar('GPG_PATH') |
Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 13 | self.gpg_version = self.get_gpg_version() |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 14 | self.rpm_bin = bb.utils.which(os.getenv('PATH'), "rpmsign") |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 15 | self.gpg_agent_bin = bb.utils.which(os.getenv('PATH'), "gpg-agent") |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 16 | |
| 17 | def export_pubkey(self, output_file, keyid, armor=True): |
| 18 | """Export GPG public key to a file""" |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 19 | cmd = '%s --no-permission-warning --batch --yes --export -o %s ' % \ |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 20 | (self.gpg_bin, output_file) |
| 21 | if self.gpg_path: |
| 22 | cmd += "--homedir %s " % self.gpg_path |
| 23 | if armor: |
| 24 | cmd += "--armor " |
| 25 | cmd += keyid |
| 26 | status, output = oe.utils.getstatusoutput(cmd) |
| 27 | if status: |
| 28 | raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % |
| 29 | (keyid, output)) |
| 30 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 31 | def sign_rpms(self, files, keyid, passphrase, digest, sign_chunk, fsk=None, fsk_password=None): |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 32 | """Sign RPM files""" |
| 33 | |
| 34 | cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % keyid |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 35 | gpg_args = '--no-permission-warning --batch --passphrase=%s --agent-program=%s|--auto-expand-secmem' % (passphrase, self.gpg_agent_bin) |
Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 36 | if self.gpg_version > (2,1,): |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 37 | gpg_args += ' --pinentry-mode=loopback' |
| 38 | cmd += "--define '_gpg_sign_cmd_extra_args %s' " % gpg_args |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 39 | cmd += "--define '_binary_filedigest_algorithm %s' " % digest |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 40 | if self.gpg_bin: |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 41 | cmd += "--define '__gpg %s' " % self.gpg_bin |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 42 | if self.gpg_path: |
| 43 | cmd += "--define '_gpg_path %s' " % self.gpg_path |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 44 | if fsk: |
| 45 | cmd += "--signfiles --fskpath %s " % fsk |
| 46 | if fsk_password: |
| 47 | cmd += "--define '_file_signing_key_password %s' " % fsk_password |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 48 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 49 | # Sign in chunks |
| 50 | for i in range(0, len(files), sign_chunk): |
| 51 | status, output = oe.utils.getstatusoutput(cmd + ' '.join(files[i:i+sign_chunk])) |
Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 52 | if status: |
| 53 | raise bb.build.FuncFailed("Failed to sign RPM packages: %s" % output) |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 54 | |
| 55 | def detach_sign(self, input_file, keyid, passphrase_file, passphrase=None, armor=True): |
| 56 | """Create a detached signature of a file""" |
| 57 | import subprocess |
| 58 | |
| 59 | if passphrase_file and passphrase: |
| 60 | raise Exception("You should use either passphrase_file of passphrase, not both") |
| 61 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 62 | cmd = [self.gpg_bin, '--detach-sign', '--no-permission-warning', '--batch', |
| 63 | '--no-tty', '--yes', '--passphrase-fd', '0', '-u', keyid] |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 64 | |
| 65 | if self.gpg_path: |
| 66 | cmd += ['--homedir', self.gpg_path] |
| 67 | if armor: |
| 68 | cmd += ['--armor'] |
| 69 | |
| 70 | #gpg > 2.1 supports password pipes only through the loopback interface |
| 71 | #gpg < 2.1 errors out if given unknown parameters |
Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 72 | if self.gpg_version > (2,1,): |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 73 | cmd += ['--pinentry-mode', 'loopback'] |
| 74 | |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 75 | if self.gpg_agent_bin: |
| 76 | cmd += ["--agent-program=%s|--auto-expand-secmem" % (self.gpg_agent_bin)] |
| 77 | |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 78 | cmd += [input_file] |
| 79 | |
| 80 | try: |
| 81 | if passphrase_file: |
| 82 | with open(passphrase_file) as fobj: |
| 83 | passphrase = fobj.readline(); |
| 84 | |
| 85 | job = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 86 | (_, stderr) = job.communicate(passphrase.encode("utf-8")) |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 87 | |
| 88 | if job.returncode: |
| 89 | raise bb.build.FuncFailed("GPG exited with code %d: %s" % |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 90 | (job.returncode, stderr.decode("utf-8"))) |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 91 | |
| 92 | except IOError as e: |
| 93 | bb.error("IO error (%s): %s" % (e.errno, e.strerror)) |
| 94 | raise Exception("Failed to sign '%s'" % input_file) |
| 95 | |
| 96 | except OSError as e: |
| 97 | bb.error("OS error (%s): %s" % (e.errno, e.strerror)) |
| 98 | raise Exception("Failed to sign '%s" % input_file) |
| 99 | |
| 100 | |
| 101 | def get_gpg_version(self): |
Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 102 | """Return the gpg version as a tuple of ints""" |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 103 | import subprocess |
| 104 | try: |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 105 | ver_str = subprocess.check_output((self.gpg_bin, "--version", "--no-permission-warning")).split()[2].decode("utf-8") |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 106 | return tuple([int(i) for i in ver_str.split("-")[0].split('.')]) |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 107 | except subprocess.CalledProcessError as e: |
| 108 | raise bb.build.FuncFailed("Could not get gpg version: %s" % e) |
| 109 | |
| 110 | |
| 111 | def verify(self, sig_file): |
| 112 | """Verify signature""" |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 113 | cmd = self.gpg_bin + " --verify --no-permission-warning " |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 114 | if self.gpg_path: |
| 115 | cmd += "--homedir %s " % self.gpg_path |
| 116 | cmd += sig_file |
| 117 | status, _ = oe.utils.getstatusoutput(cmd) |
| 118 | ret = False if status else True |
| 119 | return ret |
| 120 | |
| 121 | |
| 122 | def get_signer(d, backend): |
| 123 | """Get signer object for the specified backend""" |
| 124 | # Use local signing by default |
| 125 | if backend == 'local': |
| 126 | return LocalSigner(d) |
| 127 | else: |
| 128 | bb.fatal("Unsupported signing backend '%s'" % backend) |