Skip to content

Commit 5fa9c9e

Browse files
committed
CLI: rework PGP verification
The verification of PGP signatures had some flaws and didn't work, because the Python API and the GPG interface have changed. Inline signatures were not detected, because of a comparison of string and byte array. And even after this the code failed, because `sig.status` is no longer available.
1 parent 03c1051 commit 5fa9c9e

File tree

1 file changed

+71
-141
lines changed

1 file changed

+71
-141
lines changed

bmaptools/CLI.py

Lines changed: 71 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -128,74 +128,58 @@ def open_block_device(path):
128128
return NamedFile(file_obj, path)
129129

130130

131-
def report_verification_results(context, sigs):
132-
"""
133-
This is a helper function which reports the GPG signature verification
134-
results. The 'context' argument is the gpg context object, and the 'sigs'
135-
argument contains the results of the 'gpg.verify()' function.
131+
def verify_bmap_signature(args, bmap_obj, bmap_path):
136132
"""
133+
Verify GPG signature of the bmap file if it is present. The signature may
134+
be in a separate file (detached) or it may be inside the bmap file itself
135+
(clearsign signature).
137136
138-
import gpg
139-
140-
for sig in sigs:
141-
if (sig.summary & gpg.constants.SIGSUM_VALID) != 0:
142-
key = context.get_key(sig.fpr)
143-
author = "%s <%s>" % (key.uids[0].name, key.uids[0].email)
144-
log.info(
145-
"successfully verified bmap file signature of %s "
146-
"(fingerprint %s)" % (author, sig.fpr)
147-
)
148-
else:
149-
error_out(
150-
"signature verification failed (fingerprint %s): %s\n"
151-
"Either fix the problem or use --no-sig-verify to "
152-
"disable signature verification",
153-
sig.fpr,
154-
sig.status[2].lower(),
155-
)
156-
137+
If user specifies the --bmap-sig option, the signature is assumed to be
138+
detached and is taken from the user-specified file. Otherwise, this
139+
function verifies whether the bmap file has clearsign signature, and if
140+
not, it tries to automatically discover the detached signature by searching
141+
for a ".sig" or ".asc" file at the same path and with the same basename as
142+
the bmap file. This function then verifies the signature and reports the
143+
results.
157144
158-
def verify_detached_bmap_signature(args, bmap_obj, bmap_path):
159-
"""
160-
This is a helper function for 'verify_bmap_signature()' which handles the
161-
detached signature case.
145+
In case of the clearsign signature, the bmap file has "invalid" format,
146+
meaning that the proper bmap XML contents is in the GPG clearsign
147+
container. The XML contents has to be extracted from the container before
148+
further processing. And this is be done even if user specified the
149+
--no-sig-verify option. This function returns an open file object with the
150+
extracted XML bmap file contents in this case. Otherwise, this function
151+
returns None.
162152
"""
163153

164-
if args.no_sig_verify:
154+
if not bmap_obj:
165155
return None
166156

167-
if args.bmap_sig:
157+
clearsign_marker = b"-----BEGIN PGP SIGNED MESSAGE-----"
158+
buf = bmap_obj.read(len(clearsign_marker))
159+
bmap_obj.seek(0)
160+
161+
if buf == clearsign_marker:
162+
log.info("discovered inline signature")
163+
detached_sig = None
164+
elif args.no_sig_verify:
165+
return None
166+
elif args.bmap_sig:
168167
try:
169-
sig_obj = TransRead.TransRead(args.bmap_sig)
168+
detached_sig = TransRead.TransRead(args.bmap_sig)
170169
except TransRead.Error as err:
171170
error_out("cannot open bmap signature file '%s':\n%s", args.bmap_sig, err)
172-
sig_path = args.bmap_sig
173171
else:
174172
# Check if there is a stand-alone signature file
175173
try:
176-
sig_path = bmap_path + ".asc"
177-
sig_obj = TransRead.TransRead(sig_path)
174+
detached_sig = TransRead.TransRead(bmap_path + ".asc")
178175
except TransRead.Error:
179176
try:
180-
sig_path = bmap_path + ".sig"
181-
sig_obj = TransRead.TransRead(sig_path)
177+
detached_sig = TransRead.TransRead(bmap_path + ".sig")
182178
except TransRead.Error:
183-
# No signatures found
179+
# No detached signatures found
184180
return None
185181

186-
log.info("discovered signature file for bmap '%s'" % sig_path)
187-
188-
# If the stand-alone signature file is not local, make a local copy
189-
if sig_obj.is_url:
190-
try:
191-
tmp_obj = tempfile.NamedTemporaryFile("wb+")
192-
except IOError as err:
193-
error_out("cannot create a temporary file for the signature:\n%s", err)
194-
195-
shutil.copyfileobj(sig_obj, tmp_obj)
196-
tmp_obj.seek(0)
197-
sig_obj.close()
198-
sig_obj = tmp_obj
182+
log.info("discovered signature file for bmap '%s'" % detached_sig.name)
199183

200184
try:
201185
import gpg
@@ -207,124 +191,70 @@ def verify_detached_bmap_signature(args, bmap_obj, bmap_path):
207191
)
208192

209193
try:
210-
context = gpg.Context()
211-
signature = io.FileIO(sig_obj.name)
212-
signed_data = io.FileIO(bmap_obj.name)
213-
sigs = context.verify(signed_data, signature, None)[1].signatures
214-
except gpg.errors.GPGMEError as err:
215-
error_out(
216-
"failure when trying to verify GPG signature: %s\n"
217-
'Make sure file "%s" has proper GPG format',
218-
err.getstring(),
219-
sig_path,
220-
)
221-
except gpg.errors.BadSignatures as err:
222-
error_out("discovered a BAD GPG signature: %s\n", sig_path)
223-
224-
sig_obj.close()
225-
226-
if len(sigs) == 0:
227-
log.warning(
228-
'the "%s" signature file does not actually contain '
229-
"any valid signatures" % sig_path
230-
)
231-
else:
232-
report_verification_results(context, sigs)
233-
234-
return None
235-
194+
bmap_data = bmap_obj.read()
195+
bmap_obj.seek(0)
236196

237-
def verify_clearsign_bmap_signature(args, bmap_obj):
238-
"""
239-
This is a helper function for 'verify_bmap_signature()' which handles the
240-
clarsign signature case.
241-
"""
242-
243-
if args.bmap_sig:
244-
error_out(
245-
"the bmap file has clearsign format and already contains "
246-
"the signature, so --bmap-sig option should not be used"
247-
)
248-
249-
try:
250-
import gpg
251-
except ImportError:
252-
error_out(
253-
'cannot verify the signature because the python "gpg"'
254-
"module is not installed on your system\nCannot extract "
255-
"block map from the bmap file which has clearsign format, "
256-
"please, install the module"
257-
)
197+
if detached_sig:
198+
det_sig_data = detached_sig.read()
199+
detached_sig.close()
200+
else:
201+
det_sig_data = None
258202

259-
try:
260203
context = gpg.Context()
261-
signature = io.FileIO(bmap_obj.name)
262-
plaintext = io.BytesIO()
263-
sigs = context.verify(plaintext, signature, None)
204+
plaintext, sigs = context.verify(bmap_data, det_sig_data)
205+
sigs = sigs.signatures
264206
except gpg.errors.GPGMEError as err:
265207
error_out(
266208
"failure when trying to verify GPG signature: %s\n"
267209
"make sure the bmap file has proper GPG format",
268210
err[2].lower(),
269211
)
270212
except gpg.errors.BadSignatures as err:
271-
error_out("discovered a BAD GPG signature: %s\n", sig_path)
213+
error_out(
214+
"discovered a BAD GPG signature: %s\n",
215+
detached_sig.name if detached_sig else bmap_obj.name
216+
)
272217

273218
if not args.no_sig_verify:
274219
if len(sigs) == 0:
275220
log.warning(
221+
'the "%s" signature file does not actually contain '
222+
"any valid signatures" % detached_sig.name if detached_sig
223+
else
276224
"the bmap file clearsign signature does not actually "
277225
"contain any valid signatures"
278226
)
279227
else:
280-
report_verification_results(context, sigs)
228+
for sig in sigs:
229+
if (sig.summary & gpg.constants.SIGSUM_VALID) != 0:
230+
key = context.get_key(sig.fpr)
231+
author = "%s <%s>" % (key.uids[0].name, key.uids[0].email)
232+
log.info(
233+
"successfully verified bmap file signature of %s "
234+
"(fingerprint %s)" % (author, sig.fpr)
235+
)
236+
else:
237+
error_out(
238+
"signature verification failed (fingerprint %s)\n"
239+
"Either fix the problem or use --no-sig-verify to "
240+
"disable signature verification",
241+
sig.fpr,
242+
)
243+
244+
if detached_sig:
245+
# for detached signatures we are done
246+
return None
281247

282248
try:
283-
tmp_obj = tempfile.TemporaryFile("w+")
249+
tmp_obj = tempfile.TemporaryFile("w+b")
284250
except IOError as err:
285251
error_out("cannot create a temporary file for bmap:\n%s", err)
286252

287-
tmp_obj.write(plaintext.getvalue())
253+
tmp_obj.write(plaintext)
288254
tmp_obj.seek(0)
289255
return tmp_obj
290256

291257

292-
def verify_bmap_signature(args, bmap_obj, bmap_path):
293-
"""
294-
Verify GPG signature of the bmap file if it is present. The signature may
295-
be in a separate file (detached) or it may be inside the bmap file itself
296-
(clearsign signature).
297-
298-
If user specifies the --bmap-sig option, the signature is assumed to be
299-
detached and is taken from the user-specified file. Otherwise, this
300-
function verifies whether the bmap file has clearsign signature, and if
301-
not, it tries to automatically discover the detached signature by searching
302-
for a ".sig" or ".asc" file at the same path and with the same basename as
303-
the bmap file. This function then verifies the signature and reports the
304-
results.
305-
306-
In case of the clearsign signature, the bmap file has "invalid" format,
307-
meaning that the proper bmap XML contents is in the GPG clearsign
308-
container. The XML contents has to be extracted from the container before
309-
further processing. And this is be done even if user specified the
310-
--no-sig-verify option. This function returns an open file object with the
311-
extracted XML bmap file contents in this case. Otherwise, this function
312-
returns None.
313-
"""
314-
315-
if not bmap_obj:
316-
return None
317-
318-
clearsign_marker = "-----BEGIN PGP SIGNED MESSAGE-----"
319-
buf = bmap_obj.read(len(clearsign_marker))
320-
bmap_obj.seek(0)
321-
322-
if buf == clearsign_marker:
323-
return verify_clearsign_bmap_signature(args, bmap_obj)
324-
else:
325-
return verify_detached_bmap_signature(args, bmap_obj, bmap_path)
326-
327-
328258
def find_and_open_bmap(args):
329259
"""
330260
This is a helper function for 'open_files()' which discovers and opens the

0 commit comments

Comments
 (0)