From 78cf01ca6cb2093ff411f64142e3117ad3caa4ea Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Sun, 13 Sep 2026 03:24:02 +0200 Subject: [PATCH] fix(tools): SignError statt roher ValueError bei kaputtem Schluesselmaterial nl_sign.py verify/sign gaben bei syntaktisch ungueltigem --pub/--key-Inhalt (falsches DER/PEM-Format) bisher eine unbehandelte ValueError (pycryptodome teils IndexError/TypeError) an den Aufrufer durch - die CLI brach dadurch mit Python-Traceback ab statt mit der vorgesehenen "error: ..."-Meldung und Exitcode 1. Beide Backends (cryptography und pycryptodome/pycryptodomex) kapseln das Einlesen des privaten wie des oeffentlichen Schluessels jetzt in try/except und werfen SignError mit einer festen, schluesselinhaltsfreien Meldung (via "from None", damit die urspruengliche Bibliotheksausnahme nicht implizit mitgeschleppt wird). Ein ungueltiger Schluessel kann dadurch nie zu verify_image() == True fuehren. Vier neue Tests in test_nl_sign.py: je ein Modul- und CLI-Test fuer kaputtes --pub und kaputtes --key, die SignError bzw. Exitcode != 0 ohne Traceback auf stderr festnageln. --- tools/mkimage/nl_sign.py | 24 ++++++++++++--- tools/mkimage/tests/test_nl_sign.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/tools/mkimage/nl_sign.py b/tools/mkimage/nl_sign.py index 5b5e6e4..3a2681d 100644 --- a/tools/mkimage/nl_sign.py +++ b/tools/mkimage/nl_sign.py @@ -44,7 +44,11 @@ def _backend(): from cryptography.exceptions import InvalidSignature def sign(private_pem: bytes, data: bytes) -> bytes: - key = serialization.load_pem_private_key(private_pem, password=None) + try: + key = serialization.load_pem_private_key(private_pem, password=None) + except (ValueError, TypeError): + raise SignError( + "privater Schluessel ist kein gueltiger PEM-Schluessel") from None if key.key_size != KEY_BITS: raise SignError( f"Schluessel hat {key.key_size} Bit, benoetigt werden {KEY_BITS}") @@ -55,7 +59,11 @@ def _backend(): try: key = serialization.load_der_public_key(public_key_bytes) except ValueError: - key = serialization.load_pem_public_key(public_key_bytes) + try: + key = serialization.load_pem_public_key(public_key_bytes) + except (ValueError, TypeError): + raise SignError( + "oeffentlicher Schluessel ist weder gueltiges DER noch PEM") from None try: key.verify(signature, data, padding.PKCS1v15(), hashes.SHA256()) return True @@ -90,7 +98,11 @@ def _backend(): from Crypto.PublicKey import RSA def sign(private_pem: bytes, data: bytes) -> bytes: - key = RSA.import_key(private_pem) + try: + key = RSA.import_key(private_pem) + except (ValueError, TypeError, IndexError): + raise SignError( + "privater Schluessel ist kein gueltiger PEM/DER-Schluessel") from None if key.size_in_bits() != KEY_BITS: raise SignError( f"Schluessel hat {key.size_in_bits()} Bit, benoetigt werden {KEY_BITS}") @@ -98,7 +110,11 @@ def _backend(): return pkcs1_15.new(key).sign(digest) def verify(public_der: bytes, data: bytes, signature: bytes) -> bool: - key = RSA.import_key(public_der) + try: + key = RSA.import_key(public_der) + except (ValueError, TypeError, IndexError): + raise SignError( + "oeffentlicher Schluessel ist kein gueltiger PEM/DER-Schluessel") from None digest = SHA256.new(data) try: pkcs1_15.new(key).verify(digest, signature) diff --git a/tools/mkimage/tests/test_nl_sign.py b/tools/mkimage/tests/test_nl_sign.py index f1adbe0..4a441d8 100644 --- a/tools/mkimage/tests/test_nl_sign.py +++ b/tools/mkimage/tests/test_nl_sign.py @@ -84,3 +84,49 @@ def test_refuses_already_signed_looking_input(key, tmp_path): raw = b"\x00" * 1024 + b"NINL" + b"\x01" + b"\x00" * 507 with pytest.raises(nl_sign.SignError): nl_sign.sign_image(raw, key.private_pem) + + +def test_verify_rejects_garbage_pub_key(key): + """Ein kaputter --pub-Inhalt (kein gueltiges DER/PEM) muss als SignError gemeldet + werden, nicht als roher ValueError/Traceback - und darf niemals True ergeben.""" + raw = b"x" * 1024 + signed = nl_sign.sign_image(raw, key.private_pem) + with pytest.raises(nl_sign.SignError): + nl_sign.verify_image(signed, b"das ist definitiv kein Schluessel") + + +def test_sign_rejects_garbage_private_key(): + """Ein kaputter --key-Inhalt muss ebenso als SignError gemeldet werden.""" + raw = b"x" * 1024 + with pytest.raises(nl_sign.SignError): + nl_sign.sign_image(raw, b"das ist definitiv kein Schluessel") + + +def test_cli_verify_rejects_garbage_pub_key(key, tmp_path): + src = tmp_path / "in_gz.img" + dst = tmp_path / "out.img" + pub = tmp_path / "pub.der" + priv = tmp_path / "key.pem" + src.write_bytes(b"\x01\x02\x03" * 1000) + priv.write_bytes(key.private_pem) + pub.write_bytes(b"das ist definitiv kein Schluessel") + subprocess.run([sys.executable, str(TOOL), "sign", "--key", str(priv), + "--in", str(src), "--out", str(dst)], check=True) + r = subprocess.run([sys.executable, str(TOOL), "verify", "--pub", str(pub), + "--in", str(dst)], capture_output=True, text=True) + assert r.returncode != 0 + assert "Traceback" not in r.stderr + assert "error:" in r.stderr.lower() + + +def test_cli_sign_rejects_garbage_private_key(tmp_path): + src = tmp_path / "in_gz.img" + dst = tmp_path / "out.img" + priv = tmp_path / "key.pem" + src.write_bytes(b"\x01\x02\x03" * 1000) + priv.write_bytes(b"das ist definitiv kein Schluessel") + r = subprocess.run([sys.executable, str(TOOL), "sign", "--key", str(priv), + "--in", str(src), "--out", str(dst)], capture_output=True, text=True) + assert r.returncode != 0 + assert "Traceback" not in r.stderr + assert "error:" in r.stderr.lower()