MeshChatX/scripts/sign-plugin.py

159 lines
4.9 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# SPDX-License-Identifier: 0BSD
from __future__ import annotations
import argparse
import os
import subprocess
import sys
import tempfile
_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if _ROOT not in sys.path:
sys.path.insert(0, _ROOT)
from meshchatx.src.backend.plugin_signature import ( # noqa: E402
SignatureInfo,
canonical_dir_payload,
verify_dir_signature,
verify_wasm_signature,
verify_zip_signature,
write_dir_signature,
)
from meshchatx.src.backend.plugin_wasm_bundle import ( # noqa: E402
append_wasm_signature,
wasm_payload_without_signature,
)
def _sign_payload(identity: str, payload: bytes) -> bytes:
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write(payload)
tmp_path = tmp.name
rsg_path = tmp_path + ".rsg"
try:
rnid = os.environ.get("RNID", "rnid")
subprocess.run(
[rnid, "-i", identity, "-s", tmp_path],
check=True,
)
with open(rsg_path, "rb") as handle:
return handle.read()
finally:
for path in (tmp_path, rsg_path):
try:
os.remove(path)
except OSError:
pass
def _print_signature(info: SignatureInfo) -> int:
if not info.present:
print("unsigned")
return 0
if not info.valid:
if info.error:
print(f"invalid signature: {info.error}", file=sys.stderr)
else:
print("invalid signature", file=sys.stderr)
return 1
if info.trusted and info.signer_name:
print(f"valid signer={info.signer} name={info.signer_name} trusted=yes")
else:
print(f"valid signer={info.signer} trusted=no")
return 0
def cmd_sign(args: argparse.Namespace) -> int:
if not args.identity:
raise SystemExit("-identity is required")
if args.dir:
payload = canonical_dir_payload(args.dir)
rsg = _sign_payload(args.identity, payload)
write_dir_signature(args.dir, rsg)
return 0
if args.zip:
import zipfile
from meshchatx.src.backend.plugin_signature import canonical_zip_payload
with zipfile.ZipFile(args.zip) as archive:
payload = canonical_zip_payload(archive.infolist(), archive)
rsg = _sign_payload(args.identity, payload)
tmp_path = args.zip + ".signed"
with (
zipfile.ZipFile(args.zip) as reader,
zipfile.ZipFile(tmp_path, "w") as writer,
):
for info in reader.infolist():
clean = os.path.normpath(info.filename).replace("\\", "/")
if os.path.basename(clean) == "meshchatx.plugin.rsg":
continue
writer.writestr(info, reader.read(info.filename))
writer.writestr("meshchatx.plugin.rsg", rsg)
os.replace(tmp_path, args.zip)
return 0
if args.wasm:
with open(args.wasm, "rb") as handle:
wasm = handle.read()
payload = wasm_payload_without_signature(wasm)
rsg = _sign_payload(args.identity, payload)
signed = append_wasm_signature(wasm, rsg)
out_path = args.output or args.wasm
with open(out_path, "wb") as handle:
handle.write(signed)
return 0
if args.py:
with open(args.py, "rb") as handle:
payload = handle.read()
rsg = _sign_payload(args.identity, payload)
with open(args.py + ".rsg", "wb") as handle:
handle.write(rsg)
return 0
raise SystemExit("one of -dir, -zip, -wasm, or -py is required")
def cmd_verify(args: argparse.Namespace) -> int:
if args.dir:
return _print_signature(verify_dir_signature(args.dir))
if args.zip:
return _print_signature(verify_zip_signature(args.zip))
if args.wasm:
with open(args.wasm, "rb") as handle:
data = handle.read()
return _print_signature(verify_wasm_signature(data))
if args.py:
from meshchatx.src.backend.plugin_signature import verify_py_signature
return _print_signature(verify_py_signature(args.py))
raise SystemExit("one of -dir, -zip, -wasm, or -py is required")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Sign or verify MeshChatX plugins")
sub = parser.add_subparsers(dest="command", required=True)
sign = sub.add_parser("sign")
sign.add_argument("-identity", required=False)
sign.add_argument("-dir")
sign.add_argument("-zip")
sign.add_argument("-wasm")
sign.add_argument("-py")
sign.add_argument("-output")
sign.set_defaults(func=cmd_sign)
verify = sub.add_parser("verify")
verify.add_argument("-dir")
verify.add_argument("-zip")
verify.add_argument("-wasm")
verify.add_argument("-py")
verify.set_defaults(func=cmd_verify)
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())