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.
This commit is contained in:
Michael Sauer
2026-09-13 03:24:02 +02:00
parent e50659d954
commit 78cf01ca6c
2 changed files with 66 additions and 4 deletions
+20 -4
View File
@@ -44,7 +44,11 @@ def _backend():
from cryptography.exceptions import InvalidSignature from cryptography.exceptions import InvalidSignature
def sign(private_pem: bytes, data: bytes) -> bytes: 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: if key.key_size != KEY_BITS:
raise SignError( raise SignError(
f"Schluessel hat {key.key_size} Bit, benoetigt werden {KEY_BITS}") f"Schluessel hat {key.key_size} Bit, benoetigt werden {KEY_BITS}")
@@ -55,7 +59,11 @@ def _backend():
try: try:
key = serialization.load_der_public_key(public_key_bytes) key = serialization.load_der_public_key(public_key_bytes)
except ValueError: 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: try:
key.verify(signature, data, padding.PKCS1v15(), hashes.SHA256()) key.verify(signature, data, padding.PKCS1v15(), hashes.SHA256())
return True return True
@@ -90,7 +98,11 @@ def _backend():
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
def sign(private_pem: bytes, data: bytes) -> bytes: 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: if key.size_in_bits() != KEY_BITS:
raise SignError( raise SignError(
f"Schluessel hat {key.size_in_bits()} Bit, benoetigt werden {KEY_BITS}") 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) return pkcs1_15.new(key).sign(digest)
def verify(public_der: bytes, data: bytes, signature: bytes) -> bool: 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) digest = SHA256.new(data)
try: try:
pkcs1_15.new(key).verify(digest, signature) pkcs1_15.new(key).verify(digest, signature)
+46
View File
@@ -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 raw = b"\x00" * 1024 + b"NINL" + b"\x01" + b"\x00" * 507
with pytest.raises(nl_sign.SignError): with pytest.raises(nl_sign.SignError):
nl_sign.sign_image(raw, key.private_pem) 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()