summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe/gpg_sign.py
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oe/gpg_sign.py')
-rw-r--r--meta/lib/oe/gpg_sign.py58
1 files changed, 44 insertions, 14 deletions
diff --git a/meta/lib/oe/gpg_sign.py b/meta/lib/oe/gpg_sign.py
index 7634d7ef1d..ede6186c84 100644
--- a/meta/lib/oe/gpg_sign.py
+++ b/meta/lib/oe/gpg_sign.py
@@ -1,13 +1,16 @@
1# 1#
2# Copyright OpenEmbedded Contributors
3#
2# SPDX-License-Identifier: GPL-2.0-only 4# SPDX-License-Identifier: GPL-2.0-only
3# 5#
4 6
5"""Helper module for GPG signing""" 7"""Helper module for GPG signing"""
6import os
7 8
8import bb 9import bb
9import subprocess 10import os
10import shlex 11import shlex
12import subprocess
13import tempfile
11 14
12class LocalSigner(object): 15class LocalSigner(object):
13 """Class for handling local (on the build host) signing""" 16 """Class for handling local (on the build host) signing"""
@@ -58,7 +61,7 @@ class LocalSigner(object):
58 for i in range(0, len(files), sign_chunk): 61 for i in range(0, len(files), sign_chunk):
59 subprocess.check_output(shlex.split(cmd + ' '.join(files[i:i+sign_chunk])), stderr=subprocess.STDOUT) 62 subprocess.check_output(shlex.split(cmd + ' '.join(files[i:i+sign_chunk])), stderr=subprocess.STDOUT)
60 63
61 def detach_sign(self, input_file, keyid, passphrase_file, passphrase=None, armor=True): 64 def detach_sign(self, input_file, keyid, passphrase_file, passphrase=None, armor=True, output_suffix=None, use_sha256=False):
62 """Create a detached signature of a file""" 65 """Create a detached signature of a file"""
63 66
64 if passphrase_file and passphrase: 67 if passphrase_file and passphrase:
@@ -71,25 +74,35 @@ class LocalSigner(object):
71 cmd += ['--homedir', self.gpg_path] 74 cmd += ['--homedir', self.gpg_path]
72 if armor: 75 if armor:
73 cmd += ['--armor'] 76 cmd += ['--armor']
77 if use_sha256:
78 cmd += ['--digest-algo', "SHA256"]
74 79
75 #gpg > 2.1 supports password pipes only through the loopback interface 80 #gpg > 2.1 supports password pipes only through the loopback interface
76 #gpg < 2.1 errors out if given unknown parameters 81 #gpg < 2.1 errors out if given unknown parameters
77 if self.gpg_version > (2,1,): 82 if self.gpg_version > (2,1,):
78 cmd += ['--pinentry-mode', 'loopback'] 83 cmd += ['--pinentry-mode', 'loopback']
79 84
80 cmd += [input_file]
81
82 try: 85 try:
83 if passphrase_file: 86 if passphrase_file:
84 with open(passphrase_file) as fobj: 87 with open(passphrase_file) as fobj:
85 passphrase = fobj.readline(); 88 passphrase = fobj.readline();
86 89
87 job = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 90 if not output_suffix:
88 (_, stderr) = job.communicate(passphrase.encode("utf-8")) 91 output_suffix = 'asc' if armor else 'sig'
92 output_file = input_file + "." + output_suffix
93 with tempfile.TemporaryDirectory(dir=os.path.dirname(output_file)) as tmp_dir:
94 tmp_file = os.path.join(tmp_dir, os.path.basename(output_file))
95 cmd += ['-o', tmp_file]
96
97 cmd += [input_file]
98
99 job = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
100 (_, stderr) = job.communicate(passphrase.encode("utf-8"))
89 101
90 if job.returncode: 102 if job.returncode:
91 bb.fatal("GPG exited with code %d: %s" % (job.returncode, stderr.decode("utf-8"))) 103 bb.fatal("GPG exited with code %d: %s" % (job.returncode, stderr.decode("utf-8")))
92 104
105 os.rename(tmp_file, output_file)
93 except IOError as e: 106 except IOError as e:
94 bb.error("IO error (%s): %s" % (e.errno, e.strerror)) 107 bb.error("IO error (%s): %s" % (e.errno, e.strerror))
95 raise Exception("Failed to sign '%s'" % input_file) 108 raise Exception("Failed to sign '%s'" % input_file)
@@ -109,16 +122,33 @@ class LocalSigner(object):
109 bb.fatal("Could not get gpg version: %s" % e) 122 bb.fatal("Could not get gpg version: %s" % e)
110 123
111 124
112 def verify(self, sig_file): 125 def verify(self, sig_file, valid_sigs = ''):
113 """Verify signature""" 126 """Verify signature"""
114 cmd = self.gpg_cmd + [" --verify", "--no-permission-warning"] 127 cmd = self.gpg_cmd + ["--verify", "--no-permission-warning", "--status-fd", "1"]
115 if self.gpg_path: 128 if self.gpg_path:
116 cmd += ["--homedir", self.gpg_path] 129 cmd += ["--homedir", self.gpg_path]
117 130
118 cmd += [sig_file] 131 cmd += [sig_file]
119 status = subprocess.call(cmd) 132 status = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
120 ret = False if status else True 133 # Valid if any key matches if unspecified
121 return ret 134 if not valid_sigs:
135 ret = False if status.returncode else True
136 return ret
137
138 import re
139 goodsigs = []
140 sigre = re.compile(r'^\[GNUPG:\] GOODSIG (\S+)\s(.*)$')
141 for l in status.stdout.decode("utf-8").splitlines():
142 s = sigre.match(l)
143 if s:
144 goodsigs += [s.group(1)]
145
146 for sig in valid_sigs.split():
147 if sig in goodsigs:
148 return True
149 if len(goodsigs):
150 bb.warn('No accepted signatures found. Good signatures found: %s.' % ' '.join(goodsigs))
151 return False
122 152
123 153
124def get_signer(d, backend): 154def get_signer(d, backend):