Skip to content

Commit

Permalink
add --keyring and --fingerprint options
Browse files Browse the repository at this point in the history
  • Loading branch information
josch committed Jan 18, 2025
1 parent 559d42e commit 9053bcd
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 15 deletions.
63 changes: 51 additions & 12 deletions src/bmaptool/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,12 @@ class Signature(NamedTuple):
uid: str


def verify_bmap_signature_gpgme(bmap_obj, detached_sig):
def verify_bmap_signature_gpgme(bmap_obj, detached_sig, keyring):
if keyring:
error_out(
"Python gpgme binding is not able to verify "
"signatures against a custom keyring."
)
try:
import gpg
except ImportError:
Expand Down Expand Up @@ -186,8 +191,17 @@ def fpr2uid(fpr):
]


def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv):
def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv, keyring):
with tempfile.TemporaryDirectory(suffix=".bmaptool.gnupg") as td:
if keyring:
if gpgargv[0] == "gpg":
gpgargv.extend(
[
f"--homedir={td}",
"--no-default-keyring",
]
)
gpgargv.append(f"--keyring={keyring}")
if detached_sig:
with open(f"{td}/sig", "wb") as f:
shutil.copyfileobj(detached_sig, f)
Expand Down Expand Up @@ -236,13 +250,13 @@ def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv):
]


def verify_bmap_signature_gpgv(bmap_obj, detached_sig):
def verify_bmap_signature_gpgv(bmap_obj, detached_sig, keyring):
return verify_bmap_signature_gpgbin(
bmap_obj, detached_sig, ["gpgv", "--output=-", "--status-fd=2"]
bmap_obj, detached_sig, ["gpgv", "--output=-", "--status-fd=2"], keyring
)


def verify_bmap_signature_gpg(bmap_obj, detached_sig):
def verify_bmap_signature_gpg(bmap_obj, detached_sig, keyring):
return verify_bmap_signature_gpgbin(
bmap_obj,
detached_sig,
Expand All @@ -256,6 +270,7 @@ def verify_bmap_signature_gpg(bmap_obj, detached_sig):
"-",
"--status-fd=2",
],
keyring,
)


Expand Down Expand Up @@ -318,12 +333,16 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
"gpgv": verify_bmap_signature_gpgv,
}
have_method = set()
try:
import gpg

have_method.add("gpgme")
except ImportError:
pass
if not args.keyring:
# The python gpgme binding is not able to verify against a custom
# keyring. Only try this method if we have no keyring.
try:
import gpg

have_method.add("gpgme")
except ImportError:
pass
if shutil.which("gpg") is not None:
have_method.add("gpg")
if shutil.which("gpgv") is not None:
Expand All @@ -333,10 +352,10 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
error_out("Cannot verify GPG signature without GPG")

for method in ["gpgme", "gpgv", "gpg"]:
log.info(f"Trying to verify signature using {method}")
if method not in have_method:
continue
plaintext, sigs = methods[method](bmap_obj, detached_sig)
log.info(f"Trying to verify signature using {method}")
plaintext, sigs = methods[method](bmap_obj, detached_sig, args.keyring)
break
bmap_obj.seek(0)

Expand All @@ -350,6 +369,12 @@ def verify_bmap_signature(args, bmap_obj, bmap_path):
"contain any valid signatures"
)
else:
if args.fingerprint and args.fingerprint not in [sig.fpr for sig in sigs]:
error_out(
f"requested fingerprint {args.fingerprint} "
"did not sign the bmap file. Only have these sigs: "
+ ("".join([f"\n * {sig.fpr}" for sig in sigs]))
)
for sig in sigs:
if sig.valid:
log.info(
Expand Down Expand Up @@ -554,6 +579,12 @@ def copy_command(args):
if args.bmap_sig and args.no_sig_verify:
error_out("--bmap-sig and --no-sig-verify cannot be used together")

if args.no_sig_verify and args.keyring:
error_out("--no-sig-verify and --keyring cannot be used together")

if args.no_sig_verify and args.fingerprint:
error_out("--no-sig-verify and --fingerprint cannot be used together")

image_obj, dest_obj, bmap_obj, bmap_path, image_size, dest_is_blkdev = open_files(
args
)
Expand Down Expand Up @@ -779,6 +810,14 @@ def parse_arguments():
text = "do not verify bmap file GPG signature"
parser_copy.add_argument("--no-sig-verify", action="store_true", help=text)

# The --keyring option
text = "the GPG keyring to verify the GPG signature on the bmap file"
parser_copy.add_argument("--keyring", help=text)

# The --fingerprint option
text = "the GPG fingerprint that is expected to have signed the bmap file"
parser_copy.add_argument("--fingerprint", help=text)

# The --no-verify option
text = "do not verify the data checksum while writing"
parser_copy.add_argument("--no-verify", action="store_true", help=text)
Expand Down
58 changes: 55 additions & 3 deletions tests/test_CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,56 @@ def test_valid_signature(self):
b"successfully verified bmap file signature", completed_process.stderr
)

def test_valid_signature_fingerprint(self):
assert testkeys["correct"].fpr is not None
completed_process = subprocess.run(
[
"bmaptool",
"copy",
"--bmap",
"tests/test-data/signatures/test.image.bmap.v2.0correct.asc",
"--fingerprint",
testkeys["correct"].fpr,
"tests/test-data/test.image.gz",
self.tmpfile,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
self.assertEqual(completed_process.returncode, 0)
self.assertEqual(completed_process.stdout, b"")
self.assertIn(
b"successfully verified bmap file signature", completed_process.stderr
)

def test_valid_signature_fingerprint_keyring(self):
assert testkeys["correct"].fpr is not None
completed_process = subprocess.run(
[
"bmaptool",
"copy",
"--bmap",
"tests/test-data/signatures/test.image.bmap.v2.0correct.asc",
"--fingerprint",
testkeys["correct"].fpr,
"--keyring",
testkeys["correct"].gnupghome + ".keyring",
"tests/test-data/test.image.gz",
self.tmpfile,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
# should work without GNUPGHOME set because we supply --keyring
env={k: v for k, v in os.environ.items() if k != "GNUPGHOME"},
)
self.assertEqual(completed_process.returncode, 0)
self.assertEqual(completed_process.stdout, b"")
self.assertIn(
b"successfully verified bmap file signature", completed_process.stderr
)

def test_unknown_signer(self):
completed_process = subprocess.run(
[
Expand Down Expand Up @@ -152,7 +202,7 @@ def setUp(self):
if os.path.exists(key.gnupghome):
shutil.rmtree(key.gnupghome)
os.makedirs(key.gnupghome)
context = gpg.Context(home_dir=key.gnupghome)
context = gpg.Context(home_dir=key.gnupghome, armor=True)
dmkey = context.create_key(
key.uid,
algorithm="rsa3072",
Expand All @@ -161,8 +211,6 @@ def setUp(self):
certify=True,
)
key.fpr = dmkey.fpr
with open(f"{key.gnupghome}.keyring", "wb") as f:
f.write(context.key_export_minimal())
for bmapv in ["2.0", "1.4"]:
testp = "tests/test-data"
imbn = "test.image.bmap.v"
Expand All @@ -185,6 +233,10 @@ def setUp(self):
bmapcontent, mode=gpg.constants.sig.mode.DETACH
)
detsigf.write(signed_data)
# the file supplied to gpgv via --keyring must not be armored
context.armor = False
with open(f"{key.gnupghome}.keyring", "wb") as f:
f.write(context.key_export_minimal())

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

0 comments on commit 9053bcd

Please sign in to comment.