diff options
Diffstat (limited to 'meta/lib/oe/gpg_sign.py')
-rw-r--r-- | meta/lib/oe/gpg_sign.py | 58 |
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""" |
6 | import os | ||
7 | 8 | ||
8 | import bb | 9 | import bb |
9 | import subprocess | 10 | import os |
10 | import shlex | 11 | import shlex |
12 | import subprocess | ||
13 | import tempfile | ||
11 | 14 | ||
12 | class LocalSigner(object): | 15 | class 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 | ||
124 | def get_signer(d, backend): | 154 | def get_signer(d, backend): |