Skip to content

Commit 0536cf7

Browse files
committed
add --keyring and --fingerprint options
1 parent 64f3b05 commit 0536cf7

File tree

2 files changed

+133
-15
lines changed

2 files changed

+133
-15
lines changed

src/bmaptool/CLI.py

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,12 @@ class Signature(NamedTuple):
138138
uid: str
139139

140140

141-
def verify_bmap_signature_gpgme(bmap_obj, detached_sig):
141+
def verify_bmap_signature_gpgme(bmap_obj, detached_sig, keyring):
142+
if keyring:
143+
error_out(
144+
"Python gpgme binding is not able to verify "
145+
"signatures against a custom keyring."
146+
)
142147
try:
143148
import gpg
144149
except ImportError:
@@ -186,8 +191,17 @@ def fpr2uid(fpr):
186191
]
187192

188193

189-
def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv):
194+
def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv, keyring):
190195
with tempfile.TemporaryDirectory(suffix=".bmaptool.gnupg") as td:
196+
if keyring:
197+
if gpgargv[0] == "gpg":
198+
gpgargv.extend(
199+
[
200+
f"--homedir={td}",
201+
"--no-default-keyring",
202+
]
203+
)
204+
gpgargv.append(f"--keyring={keyring}")
191205
if detached_sig:
192206
with open(f"{td}/sig", "wb") as f:
193207
shutil.copyfileobj(detached_sig, f)
@@ -236,13 +250,13 @@ def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv):
236250
]
237251

238252

239-
def verify_bmap_signature_gpgv(bmap_obj, detached_sig):
253+
def verify_bmap_signature_gpgv(bmap_obj, detached_sig, keyring):
240254
return verify_bmap_signature_gpgbin(
241-
bmap_obj, detached_sig, ["gpgv", "--output=-", "--status-fd=2"]
255+
bmap_obj, detached_sig, ["gpgv", "--output=-", "--status-fd=2"], keyring
242256
)
243257

244258

245-
def verify_bmap_signature_gpg(bmap_obj, detached_sig):
259+
def verify_bmap_signature_gpg(bmap_obj, detached_sig, keyring):
246260
return verify_bmap_signature_gpgbin(
247261
bmap_obj,
248262
detached_sig,
@@ -256,6 +270,7 @@ def verify_bmap_signature_gpg(bmap_obj, detached_sig):
256270
"-",
257271
"--status-fd=2",
258272
],
273+
keyring,
259274
)
260275

261276

@@ -308,6 +323,10 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
308323
detached_sig = TransRead.TransRead(bmap_path + ".sig")
309324
except TransRead.Error:
310325
# No detached signatures found
326+
if args.fingerprint:
327+
error_out("no signature found but --fingerprint given")
328+
if args.keyring:
329+
error_out("no signature found but --keyring given")
311330
return None
312331

313332
log.info("discovered signature file for bmap '%s'" % detached_sig.name)
@@ -318,12 +337,16 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
318337
"gpgv": verify_bmap_signature_gpgv,
319338
}
320339
have_method = set()
321-
try:
322-
import gpg
323340

324-
have_method.add("gpgme")
325-
except ImportError:
326-
pass
341+
if not args.keyring:
342+
# The python gpgme binding is not able to verify against a custom
343+
# keyring. Only try this method if we have no keyring.
344+
try:
345+
import gpg
346+
347+
have_method.add("gpgme")
348+
except ImportError:
349+
pass
327350
if shutil.which("gpg") is not None:
328351
have_method.add("gpg")
329352
if shutil.which("gpgv") is not None:
@@ -333,10 +356,10 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
333356
error_out("Cannot verify GPG signature without GPG")
334357

335358
for method in ["gpgme", "gpgv", "gpg"]:
336-
log.info(f"Trying to verify signature using {method}")
337359
if method not in have_method:
338360
continue
339-
plaintext, sigs = methods[method](bmap_obj, detached_sig)
361+
log.info(f"Trying to verify signature using {method}")
362+
plaintext, sigs = methods[method](bmap_obj, detached_sig, args.keyring)
340363
break
341364
bmap_obj.seek(0)
342365

@@ -350,6 +373,12 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
350373
"contain any valid signatures"
351374
)
352375
else:
376+
if args.fingerprint and args.fingerprint not in [sig.fpr for sig in sigs]:
377+
error_out(
378+
f"requested fingerprint {args.fingerprint} "
379+
"did not sign the bmap file. Only have these sigs: "
380+
+ ("".join([f"\n * {sig.fpr}" for sig in sigs]))
381+
)
353382
for sig in sigs:
354383
if sig.valid:
355384
log.info(
@@ -554,6 +583,12 @@ def copy_command(args):
554583
if args.bmap_sig and args.no_sig_verify:
555584
error_out("--bmap-sig and --no-sig-verify cannot be used together")
556585

586+
if args.no_sig_verify and args.keyring:
587+
error_out("--no-sig-verify and --keyring cannot be used together")
588+
589+
if args.no_sig_verify and args.fingerprint:
590+
error_out("--no-sig-verify and --fingerprint cannot be used together")
591+
557592
image_obj, dest_obj, bmap_obj, bmap_path, image_size, dest_is_blkdev = open_files(
558593
args
559594
)
@@ -779,6 +814,14 @@ def parse_arguments():
779814
text = "do not verify bmap file GPG signature"
780815
parser_copy.add_argument("--no-sig-verify", action="store_true", help=text)
781816

817+
# The --keyring option
818+
text = "the GPG keyring to verify the GPG signature on the bmap file"
819+
parser_copy.add_argument("--keyring", help=text)
820+
821+
# The --fingerprint option
822+
text = "the GPG fingerprint that is expected to have signed the bmap file"
823+
parser_copy.add_argument("--fingerprint", help=text)
824+
782825
# The --no-verify option
783826
text = "do not verify the data checksum while writing"
784827
parser_copy.add_argument("--no-verify", action="store_true", help=text)

tests/test_CLI.py

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,56 @@ def test_valid_signature(self):
6161
b"successfully verified bmap file signature", completed_process.stderr
6262
)
6363

64+
def test_valid_signature_fingerprint(self):
65+
assert testkeys["correct"].fpr is not None
66+
completed_process = subprocess.run(
67+
[
68+
"bmaptool",
69+
"copy",
70+
"--bmap",
71+
"tests/test-data/signatures/test.image.bmap.v2.0correct.asc",
72+
"--fingerprint",
73+
testkeys["correct"].fpr,
74+
"tests/test-data/test.image.gz",
75+
self.tmpfile,
76+
],
77+
stdout=subprocess.PIPE,
78+
stderr=subprocess.PIPE,
79+
check=False,
80+
)
81+
self.assertEqual(completed_process.returncode, 0)
82+
self.assertEqual(completed_process.stdout, b"")
83+
self.assertIn(
84+
b"successfully verified bmap file signature", completed_process.stderr
85+
)
86+
87+
def test_valid_signature_fingerprint_keyring(self):
88+
assert testkeys["correct"].fpr is not None
89+
completed_process = subprocess.run(
90+
[
91+
"bmaptool",
92+
"copy",
93+
"--bmap",
94+
"tests/test-data/signatures/test.image.bmap.v2.0correct.asc",
95+
"--fingerprint",
96+
testkeys["correct"].fpr,
97+
"--keyring",
98+
testkeys["correct"].gnupghome + ".keyring",
99+
"tests/test-data/test.image.gz",
100+
self.tmpfile,
101+
],
102+
stdout=subprocess.PIPE,
103+
stderr=subprocess.PIPE,
104+
check=False,
105+
# should work without GNUPGHOME set because we supply --keyring
106+
env={k: v for k, v in os.environ.items() if k != "GNUPGHOME"},
107+
)
108+
self.assertEqual(completed_process.returncode, 0)
109+
self.assertEqual(completed_process.stdout, b"")
110+
self.assertIn(
111+
b"successfully verified bmap file signature", completed_process.stderr
112+
)
113+
64114
def test_unknown_signer(self):
65115
completed_process = subprocess.run(
66116
[
@@ -141,6 +191,29 @@ def test_clearsign(self):
141191
b"successfully verified bmap file signature", completed_process.stderr
142192
)
143193

194+
def test_fingerprint_without_signature(self):
195+
assert testkeys["correct"].fpr is not None
196+
completed_process = subprocess.run(
197+
[
198+
"bmaptool",
199+
"copy",
200+
"--bmap",
201+
"tests/test-data/test.image.bmap.v2.0",
202+
"--fingerprint",
203+
testkeys["correct"].fpr,
204+
"tests/test-data/test.image.gz",
205+
self.tmpfile,
206+
],
207+
stdout=subprocess.PIPE,
208+
stderr=subprocess.PIPE,
209+
check=False,
210+
)
211+
self.assertEqual(completed_process.returncode, 1)
212+
self.assertEqual(completed_process.stdout, b"")
213+
self.assertIn(
214+
b"no signature found but --fingerprint given", completed_process.stderr
215+
)
216+
144217
def setUp(self):
145218
try:
146219
import gpg
@@ -152,7 +225,7 @@ def setUp(self):
152225
if os.path.exists(key.gnupghome):
153226
shutil.rmtree(key.gnupghome)
154227
os.makedirs(key.gnupghome)
155-
context = gpg.Context(home_dir=key.gnupghome)
228+
context = gpg.Context(home_dir=key.gnupghome, armor=True)
156229
dmkey = context.create_key(
157230
key.uid,
158231
algorithm="rsa3072",
@@ -161,8 +234,6 @@ def setUp(self):
161234
certify=True,
162235
)
163236
key.fpr = dmkey.fpr
164-
with open(f"{key.gnupghome}.keyring", "wb") as f:
165-
f.write(context.key_export_minimal())
166237
for bmapv in ["2.0", "1.4"]:
167238
testp = "tests/test-data"
168239
imbn = "test.image.bmap.v"
@@ -185,6 +256,10 @@ def setUp(self):
185256
bmapcontent, mode=gpg.constants.sig.mode.DETACH
186257
)
187258
detsigf.write(signed_data)
259+
# the file supplied to gpgv via --keyring must not be armored
260+
context.armor = False
261+
with open(f"{key.gnupghome}.keyring", "wb") as f:
262+
f.write(context.key_export_minimal())
188263

189264
self.tmpfile = tempfile.mkstemp(prefix="testfile_", dir=".")[1]
190265
os.environ["GNUPGHOME"] = testkeys["correct"].gnupghome

0 commit comments

Comments
 (0)