From 15114f7d1ae1d12abf5bb467c016bd278e4c1ded Mon Sep 17 00:00:00 2001 From: josch Date: Thu, 16 Jan 2025 15:42:01 +0000 Subject: [PATCH] tests/test_CLI.py: create $GNUPGHOME on the fly (#31) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests/test_CLI.py: create $GNUPGHOME on the fly - avoid expiration of keys by re-creating them - prevent gnupg version being incompatible with $GNUPGHOME in git Storing binary data is bad because: - git is not good at handling binary data - binary data is harder to inspect (remember the xz incident) * TransRead.read: pass on -1 instead of 0xFFFFFFFFFFFFFFFF Passing 0xFFFFFFFFFFFFFFFF to read causes python to complain about: OverflowError: cannot fit 'int' into an index-sized integer Signed-off-by: Jörg Sommer * tests: Rework CLI tests The current tests do not take into account whether the `gpg` package has been installed or not. If it is missing, the tests should be skipped. Furthermore, the output of the tests must be checked in order to decide whether tests fail due to an exception or whether the desired error message is displayed. Signed-off-by: Jörg Sommer * 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. Signed-off-by: Jörg Sommer * tests/test_CLI.py: factor out verification logic into its own function * add gpg verification methods using gpg and gpgv binaries * ci: Fix GPG tests Fixes up the way that the GPG tests work by adding a new "native" python test version. This is required because the python 'gpg' module *must* come from the host package in order to patch libgpgme (e.g. 'python3-gpg'). It's not possible to get this module installed with the pre-canned python versions provided by GitHub Actions, so the gpg tests are skipped for this version, but using the host native python can. --------- Signed-off-by: Jörg Sommer Co-authored-by: Jörg Sommer Co-authored-by: Joshua Watt --- .github/workflows/ci.yml | 28 +- pyproject.toml | 5 +- src/bmaptool/CLI.py | 322 ++++++++++-------- src/bmaptool/TransRead.py | 2 - ...7FF9746434704C5774BE648D49DFB1163BDFB4.rev | 35 -- ...4E440F8FDA066F62DBD7FE72FDD5E4F64B2C3B.key | 42 --- ...E64D7E26AFF87DFDD758119B30F6F57B1B6D4D.key | 42 --- tests/test-data/gnupg/pubring.kbx | Bin 2018 -> 0 bytes tests/test-data/gnupg/pubring.kbx~ | Bin 32 -> 0 bytes tests/test-data/gnupg/random_seed | Bin 600 -> 0 bytes tests/test-data/gnupg/trustdb.gpg | Bin 1280 -> 0 bytes .../signatures/test.image.bmap.v2.0.asc | 114 ------- .../test.image.bmap.v2.0.sig-by-wrong-key | Bin 438 -> 0 bytes .../signatures/test.image.bmap.v2.0.valid-sig | Bin 438 -> 0 bytes tests/test_CLI.py | 98 +++++- 15 files changed, 295 insertions(+), 393 deletions(-) delete mode 100644 tests/test-data/gnupg/openpgp-revocs.d/927FF9746434704C5774BE648D49DFB1163BDFB4.rev delete mode 100644 tests/test-data/gnupg/private-keys-v1.d/6F4E440F8FDA066F62DBD7FE72FDD5E4F64B2C3B.key delete mode 100644 tests/test-data/gnupg/private-keys-v1.d/CBE64D7E26AFF87DFDD758119B30F6F57B1B6D4D.key delete mode 100644 tests/test-data/gnupg/pubring.kbx delete mode 100644 tests/test-data/gnupg/pubring.kbx~ delete mode 100644 tests/test-data/gnupg/random_seed delete mode 100644 tests/test-data/gnupg/trustdb.gpg delete mode 100644 tests/test-data/signatures/test.image.bmap.v2.0.asc delete mode 100644 tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key delete mode 100644 tests/test-data/signatures/test.image.bmap.v2.0.valid-sig diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0d1485..e0fdde5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,26 +16,40 @@ jobs: - "3.10" - "3.11" - "3.12" + # Testing with native host python is required in order to test the + # GPG code, since it must use the host python3-gpg package + - "native" steps: - uses: actions/checkout@v4 - - name: Setup Python ${{ matrix.python-version }} + + - if: matrix.python-version != 'native' + name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + + - if: matrix.python-version == 'native' + name: Setup Native Python + run: | + sudo apt-get install -y python3 python3-pip libgpgme11-dev python3-gpg + - name: Install dependencies run: | - sudo apt-get install -y pbzip2 pigz lzop liblz4-tool libgpgme11-dev - python -m pip install --upgrade pip - pip install build + sudo apt-get install -y pbzip2 pigz lzop liblz4-tool + python3 -m pip install --upgrade pip + python3 -m pip install build + - name: Build package run: | - python -m build + python3 -m build + - name: Install package run: | - pip install -e .[dev] + python3 -m pip install -e .[dev] + - name: Run tests run: | - python -m unittest -vb + python3 -m unittest -vb lint: runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index 14ae192..6cf449a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,10 @@ name = "bmaptool" description = "BMAP tools" dynamic = ["version"] dependencies = [ - "gpg >= 1.10.0", + # NOTE: gpg is not installed because it must come from the system GPG package + # (e.g. python3-gpg on Ubuntu) and not from PyPi. The PyPi version is very old + # and no longer functions correctly + #"gpg >= 1.10.0", ] required-python = ">= 3.8" authors = [ diff --git a/src/bmaptool/CLI.py b/src/bmaptool/CLI.py index 5a0924b..3332b86 100644 --- a/src/bmaptool/CLI.py +++ b/src/bmaptool/CLI.py @@ -39,6 +39,9 @@ import shutil import io import pathlib +import subprocess +import re +from typing import NamedTuple from . import BmapCreate, BmapCopy, BmapHelpers, TransRead VERSION = "3.8.0" @@ -129,75 +132,13 @@ 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. - """ - - 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(), - ) - +class Signature(NamedTuple): + valid: bool + fpr: str + uid: str -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. - """ - - if args.no_sig_verify: - return None - - if args.bmap_sig: - try: - sig_obj = 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) - except TransRead.Error: - try: - sig_path = bmap_path + ".sig" - sig_obj = TransRead.TransRead(sig_path) - except TransRead.Error: - # No 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 +def verify_bmap_signature_gpgme(bmap_obj, detached_sig): try: import gpg except ImportError: @@ -208,86 +149,114 @@ def verify_detached_bmap_signature(args, bmap_obj, bmap_path): ) try: + bmap_data = bmap_obj.read() + + if detached_sig: + det_sig_data = detached_sig.read() + detached_sig.close() + else: + det_sig_data = None + 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 + 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 file "%s" has proper GPG format', - err.getstring(), - sig_path, + "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) - - sig_obj.close() - - if len(sigs) == 0: - log.warning( - 'the "%s" signature file does not actually contain ' - "any valid signatures" % sig_path + error_out( + "discovered a BAD GPG signature: %s\n", + detached_sig.name if detached_sig else bmap_obj.name, ) - else: - report_verification_results(context, sigs) - - return None + def fpr2uid(fpr): + key = context.get_key(fpr) + return "%s <%s>" % (key.uids[0].name, key.uids[0].email) -def verify_clearsign_bmap_signature(args, bmap_obj): - """ - This is a helper function for 'verify_bmap_signature()' which handles the - clearsign 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" + return plaintext, [ + Signature( + (sig.summary & gpg.constants.SIGSUM_VALID) != 0, + sig.fpr, + fpr2uid(sig.fpr), ) - - 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" + for sig in sigs + ] + + +def verify_bmap_signature_gpgbin(bmap_obj, detached_sig, gpgargv): + with tempfile.TemporaryDirectory(suffix=".bmaptool.gnupg") as td: + if detached_sig: + with open(f"{td}/sig", "wb") as f: + shutil.copyfileobj(detached_sig, f) + gpgargv.append(f"{td}/sig") + with open(f"{td}/bmap", "wb") as f: + shutil.copyfileobj(bmap_obj, f) + gpgargv.append(f"{td}/bmap") + sp = subprocess.Popen( + gpgargv, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ) - - try: - context = gpg.Context() - signature = io.FileIO(bmap_obj.name) - plaintext = io.BytesIO() - sigs = context.verify(plaintext, signature, None) - 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(), + (output, error) = sp.communicate() + if sp.returncode > 0: + if error.find(b"[GNUPG:] NO_PUBKEY "): + error_out("No matching key found") + error_out("Failed to validate PGP signature") + + # regexes are from patatt and b4 + short_fpr = None + uid = None + gs_matches = re.search( + rb"^\[GNUPG:] GOODSIG ([0-9A-F]+)\s+(.*)$", error, flags=re.M ) - except gpg.errors.BadSignatures as err: - error_out("discovered a BAD GPG signature: %s\n", sig_path) - - if not args.no_sig_verify: - if len(sigs) == 0: - log.warning( - "the bmap file clearsign signature does not actually " - "contain any valid signatures" + if gs_matches: + good = True + short_fpr, uid = gs_matches.groups() + vs_matches = re.search( + rb"^\[GNUPG:] VALIDSIG ([0-9A-F]+) (\d{4}-\d{2}-\d{2}) (\d+)", + error, + flags=re.M, + ) + if vs_matches: + valid = True + fpr, signdate, signepoch = vs_matches.groups() + if not fpr.endswith(short_fpr): + error_out("good fingerprint does not match valid fingerprint") + if (b': Good signature from "' + uid + b'"') not in error: + log.warning("Unable to find good signature in gpg stderr output") + return output, [ + Signature( + good and valid, + fpr.decode(), + uid.decode(), ) - else: - report_verification_results(context, sigs) + ] - try: - tmp_obj = tempfile.TemporaryFile("w+") - except IOError as err: - error_out("cannot create a temporary file for bmap:\n%s", err) - tmp_obj.write(plaintext.getvalue()) - tmp_obj.seek(0) - return tmp_obj +def verify_bmap_signature_gpgv(bmap_obj, detached_sig): + return verify_bmap_signature_gpgbin( + bmap_obj, detached_sig, ["gpgv", "--status-fd=2"] + ) + + +def verify_bmap_signature_gpg(bmap_obj, detached_sig): + return verify_bmap_signature_gpgbin( + bmap_obj, + detached_sig, + [ + "gpg", + "--batch", + "--no-auto-key-retrieve", + "--no-auto-check-trustdb", + "--verify", + "--output", + "-", + "--status-fd=2", + ], + ) def verify_bmap_signature(args, bmap_obj, bmap_path): @@ -316,14 +285,97 @@ def verify_bmap_signature(args, bmap_obj, bmap_path): if not bmap_obj: return None - clearsign_marker = "-----BEGIN PGP SIGNED MESSAGE-----" + clearsign_marker = b"-----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) + log.info("discovered inline signature") + detached_sig = None + elif args.no_sig_verify: + return None + elif args.bmap_sig: + try: + 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) else: - return verify_detached_bmap_signature(args, bmap_obj, bmap_path) + # Check if there is a stand-alone signature file + try: + detached_sig = TransRead.TransRead(bmap_path + ".asc") + except TransRead.Error: + try: + detached_sig = TransRead.TransRead(bmap_path + ".sig") + except TransRead.Error: + # No detached signatures found + return None + + log.info("discovered signature file for bmap '%s'" % detached_sig.name) + + methods = { + "gpgme": verify_bmap_signature_gpgme, + "gpg": verify_bmap_signature_gpg, + "gpgv": verify_bmap_signature_gpgv, + } + have_method = set() + 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: + have_method.add("gpgv") + + if not have_method: + 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) + break + bmap_obj.seek(0) + + 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: + for sig in sigs: + if sig.valid: + log.info( + "successfully verified bmap file signature of %s " + "(fingerprint %s)" % (sig.uid, 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+b") + except IOError as err: + error_out("cannot create a temporary file for bmap:\n%s", err) + + tmp_obj.write(plaintext) + tmp_obj.seek(0) + return tmp_obj def find_and_open_bmap(args): diff --git a/src/bmaptool/TransRead.py b/src/bmaptool/TransRead.py index 3e53256..a9f95f6 100644 --- a/src/bmaptool/TransRead.py +++ b/src/bmaptool/TransRead.py @@ -658,8 +658,6 @@ def read(self, size=-1): necessary. """ - if size < 0: - size = 0xFFFFFFFFFFFFFFFF buf = self._f_objs[-1].read(size) self._pos += len(buf) diff --git a/tests/test-data/gnupg/openpgp-revocs.d/927FF9746434704C5774BE648D49DFB1163BDFB4.rev b/tests/test-data/gnupg/openpgp-revocs.d/927FF9746434704C5774BE648D49DFB1163BDFB4.rev deleted file mode 100644 index 6adad84..0000000 --- a/tests/test-data/gnupg/openpgp-revocs.d/927FF9746434704C5774BE648D49DFB1163BDFB4.rev +++ /dev/null @@ -1,35 +0,0 @@ -This is a revocation certificate for the OpenPGP key: - -pub rsa3072 2022-06-13 [SC] [expires: 2024-06-12] - 927FF9746434704C5774BE648D49DFB1163BDFB4 -uid Testkey bmaptool (Do not use, private key published!) - -A revocation certificate is a kind of "kill switch" to publicly -declare that a key shall not anymore be used. It is not possible -to retract such a revocation certificate once it has been published. - -Use it to revoke this key in case of a compromise or loss of -the secret key. However, if the secret key is still accessible, -it is better to generate a new revocation certificate and give -a reason for the revocation. For details see the description of -of the gpg command "--generate-revocation" in the GnuPG manual. - -To avoid an accidental use of this file, a colon has been inserted -before the 5 dashes below. Remove this colon with a text editor -before importing and publishing this revocation certificate. - -:-----BEGIN PGP PUBLIC KEY BLOCK----- -Comment: This is a revocation certificate - -iQG2BCABCgAgFiEEkn/5dGQ0cExXdL5kjUnfsRY737QFAmKnmGQCHQAACgkQjUnf -sRY737Q/cAv/RqUa+sVKgLyqKpk1scDlzvGeyCYSnOUbf8SHAHI9X1ZUlV4Vcy0J -LcJghlfDvffjenzTmoALaNJRrTMYjpE/Sl47qwEsI84kNscZumJvFabyYIl4hdmD -KH6dZ7X0asPpgNJ1K8Cp0hndkudxxU8DuePTxyO5fKhnvEqU+eaW/i2zEC36BQZi -WB9smT/UrqxDUFTQ4Oo84d36lqnHaHnz3acSHY6Rb8eKFq609NgFfKVTN4+Gxl28 -jwCGpzJm9PIj8bMQgyB/1Fjvt9pzhLU3OqVx9wrmDrir6ecmR+rxSqgOKAwzn92E -JLPF1wYKI89UW6t413DSDfuYUICO+QWhW8tyomw3KcPDwks7C+YGu8W2YGyWhdlM -+ac6WUAaHrmkEXqTb9+sAmyNhq3D6EiAnaZndofJxgiM7AuC+hXZjnRf7eJ1Agu9 -PXNo0/DPiB2aAY2lhT5dkYYMzwEK9v5BPe9h7HI6Ou2f8fGqwUTidg3nNAntqqFQ -ecS9iokUZOkJ -=qWLD ------END PGP PUBLIC KEY BLOCK----- diff --git a/tests/test-data/gnupg/private-keys-v1.d/6F4E440F8FDA066F62DBD7FE72FDD5E4F64B2C3B.key b/tests/test-data/gnupg/private-keys-v1.d/6F4E440F8FDA066F62DBD7FE72FDD5E4F64B2C3B.key deleted file mode 100644 index 942dae7..0000000 --- a/tests/test-data/gnupg/private-keys-v1.d/6F4E440F8FDA066F62DBD7FE72FDD5E4F64B2C3B.key +++ /dev/null @@ -1,42 +0,0 @@ -Key: (private-key (rsa (n #00C679F02C9F5BE4787D937D65C98D856462F544F7C3 - 58937303C7096AD97E8EB27E27986AF47B8D017773A52B20D66E117B7D0CE94552A586 - AC819E3BEC7E136FE18273C96F407D1680AF9A3B6FB79C4B1B0EC736A7F3DF3BF8FB47 - C57549985972A494F8412231308EB67BB83DBB17807352976A5F1C120A61BC7D0E9339 - B5354D92ED5395B5555DEFAD823C124CED3424F12A710FC73A2523E3A81B929C7BAD8A - 269409EA27B3B1CF3196AE0DC0E687633BAB43DA1ABB7227B853173149D0A782C5A25B - C44F25D5B55542FFFD78CDABB6D20DF10EA8B2CF069DD10E35C32BEDE755724222CEBD - B2E187E048A629A1D536803A547C332D8DB1AC83CA2492843335F6594CAF1A6EC6B251 - 5EAB26720C4107CAAC9AE1E43F47AA429AA8E916451F3A8C608FE315FA20E746F369B7 - 56F5EFFB5F66F1F108573606E8002B319AE042D77A3B861308FFE3BFD0F958B792AD4B - B4C6A28E28511289581E2A54A14ECCBEFCAEEC25309620EE4564B68CE40E8F9DEC73D9 - 0D4CE75A01D769B3716A5E1331#)(e #010001#)(d - #0A0FE383B79E86822F479B6B5E20FD1ABE4395F803A0C974E2C3A3F0154FABB753DA - F908AF15566C351C994A8AF3240861DCD09B40E6F43A54238C1C989C39AB09E13DB280 - 1FE257923594D99F8BCCF227D0837BFB5ECB39F4A0F49E0798B00F14D7503017C93E7A - 2A3A0922A98A83220176B5F37017F1B8320546C7C6E1FD9452B6F8AE2CA051501E570B - 0BB597CDCCBF74B4849FDAA7517BB4CE707B69D081574826682C4550005B3335E2E412 - 9BFD502415C62E57FD99C776BA4BAA35864CE8B590CD55AC67761F7EDE2C94BAD0818B - AD929BE2C8C8EE7F71D219F9A2849087D1D66772D4F4646550DED35D50FD3B3AA267E2 - 1B1207066D8AE5509DAF766056DC787403B0C14FB0794D0CDACA942943CFABCF447470 - F430C8E71A6C4D1F6025901174714CF1590EAB7FBFF935C230A6606F1BB4248FA7C157 - 09C11CB7985E4FED0F55EE8E3DC99C0A7AF1DB0495FFF9520289EE45D172A551186DBF - BE982C5C1B28294DB57B91C27C327503FBC6F33B0D1FE949AEFC953E9307FF0AA6F8BB - #)(p #00C71001D7F41A281376C036F046C9B6E257760BB789B1D7DB100C7E475E17B1 - 00176D10DF954AB327BF32447437169D6A4FDE9372A24DDB141358534529454F05140C - 0CFA0BFB101FA0D28234D14144E2F803851CE36055964D755DBA2A27CEB9EC370634CD - D476CA0B7D00852C056F7E48D17CA364BA80C801602094143951BCCB0E5C2FCD5CCC54 - 6C6AB39780D790DFD42EE8AC1ADD2C8A4DEFBBBE6FA32DBAADAC39DC587FD8AED46834 - 86171805FFC6C4BF4A94029F3906413056F2A85B57#)(q - #00FF3F01CC7946963D4C1AF299A4294FB2F9A4CC99CD57093D0E14FFBB6BB906943F - FCDBCBC9154D6F4D8A07D61E389D3AF2867D81595D8D4E770BF73B486C4AF65465DCDE - DB9E4589D821DC06C6A7042C18B881604F35A2FD2966E29ABACE47C26D1936B81A0AB8 - 00D4948F1D555D83A38B408ECC8192DA10B6C33BC5001FDD5A742DFB1366992E9A02AE - A996118522C6006A6E6AAD6DC184022648C45578EDA67A6D5A24550A6EFC49A7CBA1C0 - EBADB71C86B0C943A3DF04D3C1B8AFB3EC78B7#)(u - #00FA1D07140F156D32EFA7249DE7DCBF10EEA8C9E9C5D97BABEA0323FEDD17F6D8EB - 1E0F83FE07263541EA5C24E0C2D049B355628F022F68059C53E0C4788BEBD698734D19 - 91CC0F7F72EEE831D8875C5FF9A288B8AE6EE774B0A611FC7D4AE17A5F042C49F9E281 - 6C62B06E8D73B64578D4CFE2E14255665588C9D71DC39FB5B7FADD767F82F0D7505654 - 9A2B9383617FE093E7CCC83AFA063A0DBAE41CDEBAA972B14BEF89CB539733D0BB96E4 - F2D3B005273FA9CFCAD2763363CE38D231A3FF#))) -Created: 20220613T200440 diff --git a/tests/test-data/gnupg/private-keys-v1.d/CBE64D7E26AFF87DFDD758119B30F6F57B1B6D4D.key b/tests/test-data/gnupg/private-keys-v1.d/CBE64D7E26AFF87DFDD758119B30F6F57B1B6D4D.key deleted file mode 100644 index a377c2e..0000000 --- a/tests/test-data/gnupg/private-keys-v1.d/CBE64D7E26AFF87DFDD758119B30F6F57B1B6D4D.key +++ /dev/null @@ -1,42 +0,0 @@ -Key: (private-key (rsa (n #00E3F42F3C2AB6BCD2D0253FB78EF87BEDFA1B2BDFE5 - 0EF573FA3D5147B0A9E7E34A9E74754AA77D812A4E79370D825745350BEE383C9E2CB9 - 7F4670D15834D479AF6DB01BD6B03B10096F235EEBC756FCA3A86918CEEDEABDEF2F08 - 4927D019970ED593E86BEB821C00346B5C1D5C1C6F68882807029B455D23B3BF9B2F8C - 1E6BD8211D35CED39C67097067C9759C7E4F2DBAF361D68FE95C7D89150FBC89E4A940 - 30655A83CD837969FF8C484BCEA606B28F370878E7192F0C109A280B1090E512E4EDFB - 676A68793D8617A70DBFD1BD5C5A8B06C2043FAA5F5B7F7A28FAD06A7CEFADCD2473A4 - CA11A1DF9532C90F662CAC5679F171A1822B25DF0BA8A940E6DCDFD64CAC6DFD4B97F5 - 3AB4095812627769065A00B019EFDB5004188F27096675FF759102FD6D14F34B16DE92 - 2BC73C45F0B5B39384FBD320443A4B6733643EAFC63F9E29D369EC83133DDBCF30CD8E - 1E36C4F4BBEF1A5B6B47100B0F9FE635711C2A861006E10BDA735D1DDC2CDA05964232 - 7943B2733B54EDF1982F620551#)(e #010001#)(d - #03162E4CDE298AF20A7347C662A06C752C60655447A7B52148974CB8E7107EBC20C8 - 5CFA4CC809ADCFD36DA5CB164FBC0034432FE5577995D642C566DBDE74EB526A46D04C - FF8758CBBA1D37FAE3E39894195B08C07213F07052942085E09FF0E8E6F602229A4F54 - 311D3525B5A5B8604C2943D1C3A74E3E00DD0854B6E3DD7B3E7E8BBE1FB61BB10E0887 - 07859023805400B616C1A638BB4D5D8445F0B9963DEB7E58F567881AF9F48E46C61C40 - 65804C8B03D645B447CD890AF1329B78FD4C27540862B31E4F08CE19FE20119D130747 - 8F00B5F9F5789303847FDF7D6F4B1D33E89A6B6B49937E2E95680C672503E257027402 - 27F6C52A471FE83E7E11E88266021815535CB4CC331F91E9069DC16F55E09E91C3987E - 871F06194299436F54A248A3D09F41932C3F16FCF2A9FD576F207BE96048FB10F59AD8 - E504DE9D97EF733289FAB35B73D8CBBACD2423C011BE6F1F659FBD0378992993DC57AB - 591B395559C31B3F8E94036F13D094CE7CAD9C9266542F05F76D30C68C17BBC16C085D - #)(p #00E415B09A77BA36DEA2F523E865D420506EAED1660965850251DBEBC93FB807 - 628B1B7E117F2A33AC351ADCF7AEC8E11E7749117C50E6D93F8F6B5EE5BB30C0806ECC - 62017BA06A44D41E6AB65CF9765B74FCA085D0D7F0EC3E7099AAD81424317C930983A3 - 33D53FEDBEBEBB008CA7409523FDC360D6322E031A6AD6DB8254A90675810D8DA41C92 - E368F4F3F787463CD8A2600D9B395318386535D90511195AFF8AEB870EE45895919CB9 - 9F15D08D03E05B528812EFD7F9B506B796B549F43B#)(q - #00FFDA64D91DCAEDB0664031367847C800FA390597DFA4FDD25A5FC578BA6BB34AB2 - 30D2A0A49CA07CAB560E199AF2CD91311D85A72E55F4134105EF931C5B74EBF4AC619C - 4B345DACCFF12C786EEAC16F07C4B14CB6A37DFCCF0644BC3080F3E3B9804EFFDD6353 - F63AFA7B887EA667574D1FC7F2620A87FF1D8958695221821406C92364C89B2A063C79 - 9E6E860FDB0CC81C3DC290D171EDC4AD3BA5D9ABB12453CE7D95427B0A7912CFACA306 - 1D20174DAB9922289F6587F32704CF6F8D0FE3#)(u - #37330A6F7F514DAEC91F9E15D9A3DFA0B83A36AC5EDC6C6B1A5A842A9B6E4F92F393 - E7A245C1C7963153DEE7EA5980A26FC276CEE94D2452651C1BA3100D38ED07AB7D2091 - A940E50A8AC06064D42E29155E7105D9507CF1D33D1117F9D9D68A399DAE123ECBBDF8 - C77592D540096795DF7E4820C09D24CB6B9F4BDD8BBFCF051F555CF86AAB9E70EA0032 - CC8F191E069DEB7DC88E8128FA749AD8F10DADBAB6C557832603D55CEA0E10C2341137 - 2D33030E53CA0AB3250A8E333C6DE61E20DC#))) -Created: 20220613T200440 diff --git a/tests/test-data/gnupg/pubring.kbx b/tests/test-data/gnupg/pubring.kbx deleted file mode 100644 index e05cb284bf0e1182f0b1dfa64e240bbb9a98e4cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2018 zcmai!2T;??7RLVxNgxn<7sN#A4;KfR6xAa1ux*ez=JErL}UO#xWEf~@4Po}-po5QyZh}qXLfdGzuy7?fCPa6Fy5Nz z2k^tEBu{^D?CGB&_!12Ip<)0)&;S4f%9*j>S!81d!G+aMPQ^{u%3&rO_#Mc-F4xf$ zj&g4l*PJHA1h}^)MNsxVUi`G#UX33h0097C6}}?;*nZ@lUk$2vW_*H*k&^NqzF7?)n)Ba@orPKLo=mH znFUc**VVJ(iyHN{{pj2$q8*>E`eG_hjPGj+(`Y^?kH!s@a5^8jzdoxr(&%KpyB*nE z(KIBwELK|A4=WrLGkK-Mn{x`YKG@e*H+^;L^kc1}k;7L^&tEpuORcSpf2*3AU}W;m zl~BDeq^r)st3o{tVI}ysGJks3;#9eHe(8c7_JC>1h4dNOP2}9k71~qhwWTd@zvX2i zm&34mKnI;aW&Pn2CP`Xocjm>wcjD8`DttrNgEUPCsbr#x_W2^ackO>Z;i?(rBEMkC zO)0Zt>4jY8m?&Y+4fKIl9~$5#jRqt^0H}uldQ=AWryqa@!g&gl6-Z$tt%FGnz4LT> z5K_~Ijtrr*kl{>c>+K=G2?q$`_s_v5-gndfN81>xQpXcp3Bx7|+ zk&yyjn#4(m7*JfX!v$x_uhAEF`JizHwDlYPYiwESLNe91eqjCo# zIDU(CW(A)Lpukc@y%K!6N?ekU z4f9yH?IXEsJ!V|Y17c@^J)%rnP$*C-NK}r;O4SuLr>ZyCzEq-mp)zWVu`?aw?D`9IGX=93~8j+7z zcUj!k8m4jMrOhY$4SLg54!>S)S=#3wcuGP<{N5*%P-X2T3D~sAIMZ|gN8NE~uC*ci zL>&`zp0~{Z>Y)xlz)l9WLKOJ8ssA6`5I=GIPpsOpe5@R~x1>@IOy!W0IyqHMhSYgI zPeMkb_KxLE$uWlhvl8Aq?I@HMp%;a|nQxN55HnavY1e(&CyPniSkLO!A)V~7vzaCw z&^01i`3;U6q3yKoW6PyG3`Sn5)O3IJoy#p}jw4ks6l*mRez!pl`Q}xf&NL@Wm(|6g zUu3!Om_sv-8k@eo?5hDLV#jfdmj$MCJWIS2B3-^)#S!(tU0^?pg?ArZzspgaPR*;f z#Y7o@DH(U%z6cwj>)c|luST_u^@zsUSE5Lp)rkzFk};k9=D}3r!?TIu3RYyJ_E$_R zb3({8Zp=tK;_LlHb|1b1_jrxfWw$HeD4$5;Ocrc7A)Ma_JI|!64-e|k*X_)(zucY@ z2D?6gzW+9QW-Pk0#0<3)SX*5);e>CDot>9&PwPmr?965-3(7fu~Jo5F(y({xhorgQ$9Bf?DRHJ<~-6@3n+xmSfA9}RKE3_Jx c`5P-E3uOt7$<}uw@(!JqCtN%@uI~5#1p@bRWB>pF diff --git a/tests/test-data/gnupg/pubring.kbx~ b/tests/test-data/gnupg/pubring.kbx~ deleted file mode 100644 index fe1742ab5b88fc5199fe64e5de0453723c86eb05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32 ecmZQzU{GLWWMJ}kib!Jsf~4g$^g$TJ1_1z11qBcQ diff --git a/tests/test-data/gnupg/random_seed b/tests/test-data/gnupg/random_seed deleted file mode 100644 index 92eea16ced8873bd18e80dfb6b950c5b79643c05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmV-e0;l~!plVS*imiey$XIQ`x4}Y4F#s5SFZ-GFgQ7e6aEfW9>60hj6 z?RCuY-^Iu<;Jgq|DpRVEPKqd|H&(ltQI*PR)XB38l86TyodnCg0?`b#kyY%RVf#72 zvj8|5Dzj18c*a)#3Svd;=3Bgm%EzW+a{fwK$pzdfS!mlglj+L_s=He3_P9-rNLlL- z8vi33ufS3NYkl{zolL7H*0hlji~0*n&R=IO(nG(M$6<~^dHuCmi|5hB7yI4bNbxO9 z;W+3qW366hF7^?%pD%t*W7h!S&H|@Yg$$}2_@84dWNa2@HFrDM)| zO1J_?j66Tv22VI95~~_XH(W6wXaX}7*+?%=qFnT7FNTgjuViQ3Bs=Se~Pa`HA$=+2C5b_LSY*N~ZIrY|jExMWI m)bCSc^k6vHYqst#+gFpP!^6~jdiyICO7(Qw8d zHp3-cco-(t|13!{Dews|*_YDmd4Hpr_5Ceu5Kqe^{Lae|Qfi+jFB|zPezU?-$2TIC OS1NM%L)9UaF#rI~z8UTS diff --git a/tests/test-data/signatures/test.image.bmap.v2.0.asc b/tests/test-data/signatures/test.image.bmap.v2.0.asc deleted file mode 100644 index 8dc84fa..0000000 --- a/tests/test-data/signatures/test.image.bmap.v2.0.asc +++ /dev/null @@ -1,114 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - - - - - - - 821752 - - - 4096 - - - 201 - - - 117 - - - sha256 - - - d9cf7d44790d04fcbb089c5eeec7700e9233439ab6e4bd759035906e20f90070 - - - - 0-1 - 3-5 - 9-10 - 12 - 15-18 - 20 - 22 - 24 - 30-32 - 34-35 - 40 - 42-43 - 45 - 47 - 49-50 - 52-53 - 55-56 - 60-63 - 65-67 - 70 - 72 - 78-80 - 82-83 - 85 - 88 - 90-91 - 96 - 98-105 - 111 - 114-116 - 119-133 - 135 - 137 - 140 - 142-144 - 146-147 - 150-151 - 155 - 157 - 159-160 - 163-174 - 177 - 181-186 - 188-189 - 191 - 193 - 195 - 198-199 - - ------BEGIN PGP SIGNATURE----- - -iQGzBAEBCgAdFiEEkn/5dGQ0cExXdL5kjUnfsRY737QFAmKp2hMACgkQjUnfsRY7 -37R5kgwAvvGyq3BRzJiA+JoZbKTvQe7RA6t0mFjVozBfg8ZxpQAcqgJUR3qL72k4 -0FbOJOKrECwwxj6hfsjGHrC6cako7oqJDYwh1pal10o0sjzMT1HQiwqcmTk+VgtS -R46zB4Mz1R4IWoQcAjvXkBoxeQ+vw6SxVBPTO6a6Aa4INSFX9szxcQeh+7POGlIi -DZeWU6mLClws2OExSlcsNjttLF3EBJP7qXBPUCjiSZ1rVLtgvoVXzADYn0Em2y0+ -u2NfLOcAPAWqBJdNhXSOY+5vGfSkAN2WcQlmJiPceOlygiIVZj1WRhw6hpoAU5cM -wq2QLA0l0UQ6gq5PrF/GAnLpYlHzID6agxyGbDpcuzUq4d8IsuyF3W38SJpuDf3u -UcS/TR7l4c8t8EjxMG/L731D3n9nRy0mcHLEDKi5Afa/ppyrbp4GKmM/PO8JU1W2 -Uk8P+oUr3JFFVPdj0svHpHd9LTOjLiaWWFNiW72mSB9offswIZVBbznO+p7VYtYG -mERuBkXP -=7GH3 ------END PGP SIGNATURE----- diff --git a/tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key b/tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key deleted file mode 100644 index beea6af8eae46a9574055cc217b4602d495a135e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438 zcmV;n0ZIOe0kZ@E0SW*e79j++iUIBHOP~ohV^Y9)!SVh`%60?=0%EDw8vqIk5O~4y z{z%Gp1fI$Z{ybF?ua6%T`o44*#3Re<@sy2Baw0^C3ledgrYO*aUqH_^1Z zLhyhKF~9}1VY_ms5Ba=Dd!_LE@z^d~3cwAlAwY=o5Gn zm3Q8jmr5iXn=&@MJN}ndV`R+KqphNIys$>0qimM;CvH?aw7L#Nsy61u+DzBI?@VPE z`hy&joY15~1==IO)A!>JH5SmFytwCFM1w{ERo#3W zkFwM=r<6JN&W^kBpc{&6mw-SdjG*S6O^RJ`DBk&RxlSMYxuus8*27u^^jo@zFoT+Q zXE@rI`%vM9hHZTPpmls`>MLu^K@I#2hFIwAzuc;Re-geN9e+&J3pxW&M@~ diff --git a/tests/test-data/signatures/test.image.bmap.v2.0.valid-sig b/tests/test-data/signatures/test.image.bmap.v2.0.valid-sig deleted file mode 100644 index 728e5c04017cf56c1930f2403e9d28ca7ce16f0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438 zcmV;n0ZIOe0kZ@E0SW*e79j+ZfBAG|G;mB;biQPbN#C&+JKwYg0%EDuu>cAQ5RFOS zu@*buv`h#L0K^AGO=oV?$DM@SLO4IRp_d}ratm-B{>3xFAJpjFFgKV|CBUcx%^lw$ zSKTb%7 z4;}e&8*o%d<3`wNV%noa^qKbO8ohztq0{74HYZs?(4}z@(!Nv+n@an}%_^X45p7H9 zY3)^QpT3?x#LNaS8$Lw|mgIB5RY*d(X#G;x{9U`$Au^&sSL0nuAmuA}vc5ZpcsYcE z%bcWw8AOq2s>7hF@;~OQ>%=4x+%nZMavJ!PNFrSbtLF^!2O0sOjVHzp8|+o=wJ+V6 g4wfAs+uHhrc*q+ut#*E?5j?xTO>E#SN~aSLS<4*P^#A|> diff --git a/tests/test_CLI.py b/tests/test_CLI.py index 4683960..c4c00a4 100644 --- a/tests/test_CLI.py +++ b/tests/test_CLI.py @@ -21,6 +21,7 @@ import sys import tempfile import tests.helpers +import shutil class TestCLI(unittest.TestCase): @@ -32,15 +33,19 @@ def test_valid_signature(self): "--bmap", "tests/test-data/test.image.bmap.v2.0", "--bmap-sig", - "tests/test-data/signatures/test.image.bmap.v2.0.valid-sig", + "tests/test-data/signatures/test.image.bmap.v2.0correct.asc", "tests/test-data/test.image.gz", self.tmpfile, ], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, check=False, ) - self.assertEqual(completed_process.returncode, 0, completed_process.stdout) + 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( @@ -50,15 +55,17 @@ def test_unknown_signer(self): "--bmap", "tests/test-data/test.image.bmap.v2.0", "--bmap-sig", - "tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key", + "tests/test-data/signatures/test.image.bmap.v2.0imposter.asc", "tests/test-data/test.image.gz", self.tmpfile, ], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, check=False, ) - self.assertEqual(completed_process.returncode, 1, completed_process.stdout) + self.assertEqual(completed_process.returncode, 1) + self.assertEqual(completed_process.stdout, b"") + self.assertIn(b"discovered a BAD GPG signature", completed_process.stderr) def test_wrong_signature(self): completed_process = subprocess.run( @@ -68,15 +75,17 @@ def test_wrong_signature(self): "--bmap", "tests/test-data/test.image.bmap.v1.4", "--bmap-sig", - "tests/test-data/signatures/test.image.bmap.v2.0.valid-sig", + "tests/test-data/signatures/test.image.bmap.v2.0correct.asc", "tests/test-data/test.image.gz", self.tmpfile, ], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, check=False, ) - self.assertEqual(completed_process.returncode, 1, completed_process.stdout) + self.assertEqual(completed_process.returncode, 1) + self.assertEqual(completed_process.stdout, b"") + self.assertIn(b"discovered a BAD GPG signature", completed_process.stderr) def test_wrong_signature_uknown_signer(self): completed_process = subprocess.run( @@ -86,15 +95,17 @@ def test_wrong_signature_uknown_signer(self): "--bmap", "tests/test-data/test.image.bmap.v1.4", "--bmap-sig", - "tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key", + "tests/test-data/signatures/test.image.bmap.v2.0imposter.asc", "tests/test-data/test.image.gz", self.tmpfile, ], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, check=False, ) - self.assertEqual(completed_process.returncode, 1, completed_process.stdout) + self.assertEqual(completed_process.returncode, 1) + self.assertEqual(completed_process.stdout, b"") + self.assertIn(b"discovered a BAD GPG signature", completed_process.stderr) def test_clearsign(self): completed_process = subprocess.run( @@ -102,22 +113,79 @@ def test_clearsign(self): "bmaptool", "copy", "--bmap", - "tests/test-data/signatures/test.image.bmap.v2.0.asc", + "tests/test-data/signatures/test.image.bmap.v2.0correct.det.asc", "tests/test-data/test.image.gz", self.tmpfile, ], stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=subprocess.PIPE, check=False, ) - self.assertEqual(completed_process.returncode, 1, completed_process.stdout) + self.assertEqual(completed_process.returncode, 0) + self.assertEqual(completed_process.stdout, b"") + self.assertIn( + b"successfully verified bmap file signature", completed_process.stderr + ) def setUp(self): + try: + import gpg + except ImportError: + self.skipTest("python module 'gpg' missing") + + os.makedirs("tests/test-data/signatures", exist_ok=True) + for gnupghome, userid in [ + ("tests/test-data/gnupg/", "correct "), + ("tests/test-data/gnupg2/", "imposter "), + ]: + if os.path.exists(gnupghome): + shutil.rmtree(gnupghome) + os.makedirs(gnupghome) + context = gpg.Context(home_dir=gnupghome, armor=True) + dmkey = context.create_key( + userid, + algorithm="rsa3072", + expires_in=31536000, + sign=True, + certify=True, + ) + for bmapv in ["2.0", "1.4"]: + testp = "tests/test-data" + imbn = "test.image.bmap.v" + with open(f"{testp}/{imbn}{bmapv}", "rb") as bmapf, open( + f"{testp}/signatures/{imbn}{bmapv}{userid.split()[0]}.asc", + "wb", + ) as sigf, open( + f"{testp}/signatures/{imbn}{bmapv}{userid.split()[0]}.det.asc", + "wb", + ) as detsigf: + bmapcontent = bmapf.read() + signed_data, result = context.sign( + bmapcontent, mode=gpg.constants.sig.mode.DETACH + ) + sigf.write(signed_data) + signed_data, result = context.sign( + bmapcontent, mode=gpg.constants.sig.mode.CLEAR + ) + detsigf.write(signed_data) os.environ["GNUPGHOME"] = "tests/test-data/gnupg/" self.tmpfile = tempfile.mkstemp(prefix="testfile_", dir=".")[1] def tearDown(self): os.unlink(self.tmpfile) + for gnupghome, userid in [ + ("tests/test-data/gnupg/", "correct "), + ("tests/test-data/gnupg2/", "imposter "), + ]: + shutil.rmtree(gnupghome) + for bmapv in ["2.0", "1.4"]: + testp = "tests/test-data" + imbn = "test.image.bmap.v" + os.unlink(f"{testp}/signatures/{imbn}{bmapv}{userid.split()[0]}.asc") + os.unlink( + f"{testp}/signatures/{imbn}{bmapv}{userid.split()[0]}.det.asc" + ) + os.rmdir("tests/test-data/signatures") if __name__ == "__main__":