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 e05cb28..0000000 Binary files a/tests/test-data/gnupg/pubring.kbx and /dev/null differ diff --git a/tests/test-data/gnupg/pubring.kbx~ b/tests/test-data/gnupg/pubring.kbx~ deleted file mode 100644 index fe1742a..0000000 Binary files a/tests/test-data/gnupg/pubring.kbx~ and /dev/null differ diff --git a/tests/test-data/gnupg/random_seed b/tests/test-data/gnupg/random_seed deleted file mode 100644 index 92eea16..0000000 Binary files a/tests/test-data/gnupg/random_seed and /dev/null differ diff --git a/tests/test-data/gnupg/trustdb.gpg b/tests/test-data/gnupg/trustdb.gpg deleted file mode 100644 index aedc7bc..0000000 Binary files a/tests/test-data/gnupg/trustdb.gpg and /dev/null differ 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 beea6af..0000000 Binary files a/tests/test-data/signatures/test.image.bmap.v2.0.sig-by-wrong-key and /dev/null differ 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 728e5c0..0000000 Binary files a/tests/test-data/signatures/test.image.bmap.v2.0.valid-sig and /dev/null differ 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__":