Skip to content

Commit

Permalink
CLI: rework PGP verification
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Jörg Sommer <[email protected]>
  • Loading branch information
jo-so-nx committed Mar 9, 2024
1 parent f990459 commit 340a578
Showing 1 changed file with 71 additions and 141 deletions.
212 changes: 71 additions & 141 deletions bmaptools/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,74 +128,58 @@ def open_block_device(path):
return NamedFile(file_obj, path)


def report_verification_results(context, sigs):
"""
This is a helper function which reports the GPG signature verification
results. The 'context' argument is the gpg context object, and the 'sigs'
argument contains the results of the 'gpg.verify()' function.
def verify_bmap_signature(args, bmap_obj, bmap_path):
"""
Verify GPG signature of the bmap file if it is present. The signature may
be in a separate file (detached) or it may be inside the bmap file itself
(clearsign signature).
import gpg

for sig in sigs:
if (sig.summary & gpg.constants.SIGSUM_VALID) != 0:
key = context.get_key(sig.fpr)
author = "%s <%s>" % (key.uids[0].name, key.uids[0].email)
log.info(
"successfully verified bmap file signature of %s "
"(fingerprint %s)" % (author, sig.fpr)
)
else:
error_out(
"signature verification failed (fingerprint %s): %s\n"
"Either fix the problem or use --no-sig-verify to "
"disable signature verification",
sig.fpr,
sig.status[2].lower(),
)

If user specifies the --bmap-sig option, the signature is assumed to be
detached and is taken from the user-specified file. Otherwise, this
function verifies whether the bmap file has clearsign signature, and if
not, it tries to automatically discover the detached signature by searching
for a ".sig" or ".asc" file at the same path and with the same basename as
the bmap file. This function then verifies the signature and reports the
results.
def verify_detached_bmap_signature(args, bmap_obj, bmap_path):
"""
This is a helper function for 'verify_bmap_signature()' which handles the
detached signature case.
In case of the clearsign signature, the bmap file has "invalid" format,
meaning that the proper bmap XML contents is in the GPG clearsign
container. The XML contents has to be extracted from the container before
further processing. And this is be done even if user specified the
--no-sig-verify option. This function returns an open file object with the
extracted XML bmap file contents in this case. Otherwise, this function
returns None.
"""

if args.no_sig_verify:
if not bmap_obj:
return None

if args.bmap_sig:
clearsign_marker = b"-----BEGIN PGP SIGNED MESSAGE-----"
buf = bmap_obj.read(len(clearsign_marker))
bmap_obj.seek(0)

if buf == clearsign_marker:
log.info("discovered inline signature")
detached_sig = None
elif args.no_sig_verify:
return None
elif args.bmap_sig:
try:
sig_obj = TransRead.TransRead(args.bmap_sig)
detached_sig = TransRead.TransRead(args.bmap_sig)
except TransRead.Error as err:
error_out("cannot open bmap signature file '%s':\n%s", args.bmap_sig, err)
sig_path = args.bmap_sig
else:
# Check if there is a stand-alone signature file
try:
sig_path = bmap_path + ".asc"
sig_obj = TransRead.TransRead(sig_path)
detached_sig = TransRead.TransRead(bmap_path + ".asc")
except TransRead.Error:
try:
sig_path = bmap_path + ".sig"
sig_obj = TransRead.TransRead(sig_path)
detached_sig = TransRead.TransRead(bmap_path + ".sig")
except TransRead.Error:
# No signatures found
# No detached signatures found
return None

log.info("discovered signature file for bmap '%s'" % sig_path)

# If the stand-alone signature file is not local, make a local copy
if sig_obj.is_url:
try:
tmp_obj = tempfile.NamedTemporaryFile("wb+")
except IOError as err:
error_out("cannot create a temporary file for the signature:\n%s", err)

shutil.copyfileobj(sig_obj, tmp_obj)
tmp_obj.seek(0)
sig_obj.close()
sig_obj = tmp_obj
log.info("discovered signature file for bmap '%s'" % detached_sig.name)

try:
import gpg
Expand All @@ -207,124 +191,70 @@ def verify_detached_bmap_signature(args, bmap_obj, bmap_path):
)

try:
context = gpg.Context()
signature = io.FileIO(sig_obj.name)
signed_data = io.FileIO(bmap_obj.name)
sigs = context.verify(signed_data, signature, None)[1].signatures
except gpg.errors.GPGMEError as err:
error_out(
"failure when trying to verify GPG signature: %s\n"
'Make sure file "%s" has proper GPG format',
err.getstring(),
sig_path,
)
except gpg.errors.BadSignatures as err:
error_out("discovered a BAD GPG signature: %s\n", sig_path)

sig_obj.close()

if len(sigs) == 0:
log.warning(
'the "%s" signature file does not actually contain '
"any valid signatures" % sig_path
)
else:
report_verification_results(context, sigs)

return None

bmap_data = bmap_obj.read()
bmap_obj.seek(0)

def verify_clearsign_bmap_signature(args, bmap_obj):
"""
This is a helper function for 'verify_bmap_signature()' which handles the
clarsign signature case.
"""

if args.bmap_sig:
error_out(
"the bmap file has clearsign format and already contains "
"the signature, so --bmap-sig option should not be used"
)

try:
import gpg
except ImportError:
error_out(
'cannot verify the signature because the python "gpg"'
"module is not installed on your system\nCannot extract "
"block map from the bmap file which has clearsign format, "
"please, install the module"
)
if detached_sig:
det_sig_data = detached_sig.read()
detached_sig.close()
else:
det_sig_data = None

try:
context = gpg.Context()
signature = io.FileIO(bmap_obj.name)
plaintext = io.BytesIO()
sigs = context.verify(plaintext, signature, None)
plaintext, sigs = context.verify(bmap_data, det_sig_data)
sigs = sigs.signatures
except gpg.errors.GPGMEError as err:
error_out(
"failure when trying to verify GPG signature: %s\n"
"make sure the bmap file has proper GPG format",
err[2].lower(),
)
except gpg.errors.BadSignatures as err:
error_out("discovered a BAD GPG signature: %s\n", sig_path)
error_out(
"discovered a BAD GPG signature: %s\n",
detached_sig.name if detached_sig else bmap_obj.name
)

if not args.no_sig_verify:
if len(sigs) == 0:
log.warning(
'the "%s" signature file does not actually contain '
"any valid signatures" % detached_sig.name if detached_sig
else
"the bmap file clearsign signature does not actually "
"contain any valid signatures"
)
else:
report_verification_results(context, sigs)
for sig in sigs:
if (sig.summary & gpg.constants.SIGSUM_VALID) != 0:
key = context.get_key(sig.fpr)
author = "%s <%s>" % (key.uids[0].name, key.uids[0].email)
log.info(
"successfully verified bmap file signature of %s "
"(fingerprint %s)" % (author, sig.fpr)
)
else:
error_out(
"signature verification failed (fingerprint %s)\n"
"Either fix the problem or use --no-sig-verify to "
"disable signature verification",
sig.fpr,
)

if detached_sig:
# for detached signatures we are done
return None

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

tmp_obj.write(plaintext.getvalue())
tmp_obj.write(plaintext)
tmp_obj.seek(0)
return tmp_obj


def verify_bmap_signature(args, bmap_obj, bmap_path):
"""
Verify GPG signature of the bmap file if it is present. The signature may
be in a separate file (detached) or it may be inside the bmap file itself
(clearsign signature).
If user specifies the --bmap-sig option, the signature is assumed to be
detached and is taken from the user-specified file. Otherwise, this
function verifies whether the bmap file has clearsign signature, and if
not, it tries to automatically discover the detached signature by searching
for a ".sig" or ".asc" file at the same path and with the same basename as
the bmap file. This function then verifies the signature and reports the
results.
In case of the clearsign signature, the bmap file has "invalid" format,
meaning that the proper bmap XML contents is in the GPG clearsign
container. The XML contents has to be extracted from the container before
further processing. And this is be done even if user specified the
--no-sig-verify option. This function returns an open file object with the
extracted XML bmap file contents in this case. Otherwise, this function
returns None.
"""

if not bmap_obj:
return None

clearsign_marker = "-----BEGIN PGP SIGNED MESSAGE-----"
buf = bmap_obj.read(len(clearsign_marker))
bmap_obj.seek(0)

if buf == clearsign_marker:
return verify_clearsign_bmap_signature(args, bmap_obj)
else:
return verify_detached_bmap_signature(args, bmap_obj, bmap_path)


def find_and_open_bmap(args):
"""
This is a helper function for 'open_files()' which discovers and opens the
Expand Down

0 comments on commit 340a578

Please sign in to comment.