From 94aea6fd2e78e9b4c4b6fe9e23c521c7db5f303c Mon Sep 17 00:00:00 2001 From: AndyVale Date: Fri, 27 Mar 2026 20:08:15 +0100 Subject: [PATCH] fix AES-GCM handling --- src/worker.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/worker.py b/src/worker.py index a0296a6..cd0e883 100644 --- a/src/worker.py +++ b/src/worker.py @@ -40,9 +40,14 @@ import re import traceback from urllib.parse import urlparse, parse_qs - from workers import Response +import js +from pyodide.ffi import to_js + +def jso(obj): + """Utility to convert Python types to JS objects (Object.fromEntries for dicts).""" + return to_js(obj, create_pyproxies=False, dict_converter=js.Object.fromEntries) def capture_exception(exc: Exception, req=None, _env=None, where: str = ""): """Best-effort exception logging with full traceback and request context.""" @@ -109,12 +114,9 @@ def _derive_aes_key_bytes(secret: str) -> bytes: async def _import_aes_key(key_bytes: bytes) -> object: """Import raw bytes as a Web Crypto AES-GCM CryptoKey.""" - import js - from pyodide.ffi import to_js - key_buf = to_js(key_bytes, create_pyproxies=False) - algo = to_js({"name": "AES-GCM"}, create_pyproxies=False) - usages = to_js(["encrypt", "decrypt"], create_pyproxies=False) - return await js.crypto.subtle.importKey("raw", key_buf, algo, False, usages) + return await js.crypto.subtle.importKey( + "raw", jso(key_bytes), jso({"name": "AES-GCM"}), False, jso(["encrypt", "decrypt"]) + ) async def encrypt_aes(plaintext: str, secret: str) -> str: @@ -126,13 +128,11 @@ async def encrypt_aes(plaintext: str, secret: str) -> str: if not plaintext: return "" try: - import js - from pyodide.ffi import to_js key_bytes = _derive_aes_key_bytes(secret) crypto_key = await _import_aes_key(key_bytes) - iv = bytes(js.crypto.getRandomValues(to_js(bytearray(12)))) - algo = to_js({"name": "AES-GCM", "iv": to_js(iv)}, create_pyproxies=False) - data = to_js(plaintext.encode("utf-8"), create_pyproxies=False) + iv = bytes(js.crypto.getRandomValues(jso(bytearray(12)))) + algo = jso({"name": "AES-GCM", "iv": jso(iv)}) + data = jso(plaintext.encode("utf-8")) ct_buf = await js.crypto.subtle.encrypt(algo, crypto_key, data) ct = bytes(js.Uint8Array.new(ct_buf)) return "v1:" + base64.b64encode(iv + ct).decode("ascii") @@ -149,8 +149,6 @@ async def decrypt_aes(ciphertext: str, secret: str) -> str: return "" if not ciphertext.startswith("v1:"): return _decrypt_xor(ciphertext, secret) - import js - from pyodide.ffi import to_js try: raw = base64.b64decode(ciphertext[3:]) iv, ct = raw[:12], raw[12:] @@ -159,8 +157,8 @@ async def decrypt_aes(ciphertext: str, secret: str) -> str: return "[decryption error]" key_bytes = _derive_aes_key_bytes(secret) crypto_key = await _import_aes_key(key_bytes) - algo = to_js({"name": "AES-GCM", "iv": to_js(iv)}, create_pyproxies=False) - data = to_js(ct, create_pyproxies=False) + algo = jso({"name": "AES-GCM", "iv": jso(iv)}) + data = jso(ct) try: pt_buf = await js.crypto.subtle.decrypt(algo, crypto_key, data) return bytes(js.Uint8Array.new(pt_buf)).decode("utf-8")