|
39 | 39 | import shutil |
40 | 40 | import io |
41 | 41 | import pathlib |
| 42 | +import subprocess |
| 43 | +import re |
42 | 44 | from typing import NamedTuple |
43 | 45 | from . import BmapCreate, BmapCopy, BmapHelpers, TransRead |
44 | 46 |
|
@@ -184,6 +186,79 @@ def fpr2uid(fpr): |
184 | 186 | ] |
185 | 187 |
|
186 | 188 |
|
| 189 | +def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv): |
| 190 | + with tempfile.TemporaryDirectory(suffix=".bmaptool.gnupg") as td: |
| 191 | + if detached_sig: |
| 192 | + with open(f"{td}/sig", "wb") as f: |
| 193 | + shutil.copyfileobj(detached_sig, f) |
| 194 | + gpgargv.append(f"{td}/sig") |
| 195 | + with open(f"{td}/bmap", "wb") as f: |
| 196 | + shutil.copyfileobj(bmap_obj, f) |
| 197 | + gpgargv.append(f"{td}/bmap") |
| 198 | + sp = subprocess.Popen( |
| 199 | + gpgargv, |
| 200 | + stdout=subprocess.PIPE, |
| 201 | + stderr=subprocess.PIPE, |
| 202 | + ) |
| 203 | + (output, error) = sp.communicate() |
| 204 | + if sp.returncode > 0: |
| 205 | + if error.find(b"[GNUPG:] NO_PUBKEY "): |
| 206 | + error_out("No matching key found") |
| 207 | + error_out("Failed to validate PGP signature") |
| 208 | + |
| 209 | + # regexes are from patatt and b4 |
| 210 | + short_fpr = None |
| 211 | + uid = None |
| 212 | + gs_matches = re.search( |
| 213 | + rb"^\[GNUPG:] GOODSIG ([0-9A-F]+)\s+(.*)$", error, flags=re.M |
| 214 | + ) |
| 215 | + if gs_matches: |
| 216 | + good = True |
| 217 | + short_fpr, uid = gs_matches.groups() |
| 218 | + vs_matches = re.search( |
| 219 | + rb"^\[GNUPG:] VALIDSIG ([0-9A-F]+) (\d{4}-\d{2}-\d{2}) (\d+)", |
| 220 | + error, |
| 221 | + flags=re.M, |
| 222 | + ) |
| 223 | + if vs_matches: |
| 224 | + valid = True |
| 225 | + fpr, signdate, signepoch = vs_matches.groups() |
| 226 | + if not fpr.endswith(short_fpr): |
| 227 | + error_out("good fingerprint does not match valid fingerprint") |
| 228 | + if (b': Good signature from "' + uid + b'"') not in error: |
| 229 | + log.warning("Unable to find good signature in gpg stderr output") |
| 230 | + return output, [ |
| 231 | + Signature( |
| 232 | + good and valid, |
| 233 | + fpr.decode(), |
| 234 | + uid.decode(), |
| 235 | + ) |
| 236 | + ] |
| 237 | + |
| 238 | + |
| 239 | +def verify_bmap_signature_gpgv(bmap_obj, detached_sig): |
| 240 | + return verify_bmap_signature_gpgbin( |
| 241 | + bmap_obj, detached_sig, ["gpgv", "--status-fd=2"] |
| 242 | + ) |
| 243 | + |
| 244 | + |
| 245 | +def verify_bmap_signature_gpg(bmap_obj, detached_sig): |
| 246 | + return verify_bmap_signature_gpgbin( |
| 247 | + bmap_obj, |
| 248 | + detached_sig, |
| 249 | + [ |
| 250 | + "gpg", |
| 251 | + "--batch", |
| 252 | + "--no-auto-key-retrieve", |
| 253 | + "--no-auto-check-trustdb", |
| 254 | + "--verify", |
| 255 | + "--output", |
| 256 | + "-", |
| 257 | + "--status-fd=2", |
| 258 | + ], |
| 259 | + ) |
| 260 | + |
| 261 | + |
187 | 262 | def verify_bmap_signature(args, bmap_obj, bmap_path): |
188 | 263 | """ |
189 | 264 | Verify GPG signature of the bmap file if it is present. The signature may |
@@ -237,7 +312,32 @@ def verify_bmap_signature(args, bmap_obj, bmap_path): |
237 | 312 |
|
238 | 313 | log.info("discovered signature file for bmap '%s'" % detached_sig.name) |
239 | 314 |
|
240 | | - plaintext, sigs = verify_bmap_signature_gpgme(bmap_obj, detached_sig) |
| 315 | + methods = { |
| 316 | + "gpgme": verify_bmap_signature_gpgme, |
| 317 | + "gpg": verify_bmap_signature_gpg, |
| 318 | + "gpgv": verify_bmap_signature_gpgv, |
| 319 | + } |
| 320 | + have_method = set() |
| 321 | + try: |
| 322 | + import gpg |
| 323 | + |
| 324 | + have_method.add("gpgme") |
| 325 | + except ImportError: |
| 326 | + pass |
| 327 | + if shutil.which("gpg") is not None: |
| 328 | + have_method.add("gpg") |
| 329 | + if shutil.which("gpgv") is not None: |
| 330 | + have_method.add("gpgv") |
| 331 | + |
| 332 | + if not have_method: |
| 333 | + error_out("Cannot verify GPG signature without GPG") |
| 334 | + |
| 335 | + for method in ["gpgme", "gpgv", "gpg"]: |
| 336 | + log.info(f"Trying to verify signature using {method}") |
| 337 | + if method not in have_method: |
| 338 | + continue |
| 339 | + plaintext, sigs = methods[method](bmap_obj, detached_sig) |
| 340 | + break |
241 | 341 | bmap_obj.seek(0) |
242 | 342 |
|
243 | 343 | if not args.no_sig_verify: |
|
0 commit comments