from __future__ import annotations import argparse import json import os from pathlib import Path import pytest from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from metin_release.commands import sign from metin_release.errors import IntegrityError, KeyPermissionError, ValidationError from metin_release.workspace import Context def _ctx(script: Path) -> Context: return Context(sign_manifest_script=script) def _write_toy_manifest(path: Path) -> None: path.write_bytes(json.dumps({"version": "test", "files": []}, indent=2).encode() + b"\n") @pytest.mark.skipif( not Path("/home/jann/metin/repos/m2dev-client/scripts/sign-manifest.py").is_file(), reason="real sign-manifest.py not available", ) def test_sign_produces_valid_signature(tmp_path: Path, test_keypair, sign_manifest_script: Path): key_path, pub_hex = test_keypair manifest_path = tmp_path / "manifest.json" _write_toy_manifest(manifest_path) ctx = _ctx(sign_manifest_script) args = argparse.Namespace(manifest=manifest_path, key=key_path, out=None) result = sign.run(ctx, args) sig_path = Path(result.data["artifacts"]["signature_path"]) assert sig_path.is_file() sig = sig_path.read_bytes() assert len(sig) == 64 # Verify it actually checks out against the public key. pub = Ed25519PublicKey.from_public_bytes(bytes.fromhex(pub_hex)) pub.verify(sig, manifest_path.read_bytes()) # Now corrupt the manifest; signature no longer verifies. manifest_path.write_bytes(b"tampered") with pytest.raises(InvalidSignature): pub.verify(sig, manifest_path.read_bytes()) def test_sign_rejects_loose_key_permissions(tmp_path: Path, sign_manifest_script: Path): key = tmp_path / "key" key.write_bytes(b"\x00" * 32) os.chmod(key, 0o644) manifest = tmp_path / "manifest.json" _write_toy_manifest(manifest) ctx = _ctx(sign_manifest_script) args = argparse.Namespace(manifest=manifest, key=key, out=None) with pytest.raises(KeyPermissionError): sign.run(ctx, args) def test_sign_relative_key_path_rejected(tmp_path: Path, sign_manifest_script: Path): manifest = tmp_path / "manifest.json" _write_toy_manifest(manifest) ctx = _ctx(sign_manifest_script) args = argparse.Namespace(manifest=manifest, key=Path("relative/key"), out=None) with pytest.raises(ValidationError): sign.run(ctx, args)