"""Protect the account's token and book key at rest. Windows: DPAPI (CryptProtectData), bound to this Windows user on this machine, as tools/diora_client.py in diora-android does. Elsewhere there is no such store in the standard library: the blob is plain base64 in a 0600 file (the same exposure the web client has with its key in localStorage), marked as such so a later version can move it into a keyring. """ from __future__ import annotations import base64 import sys _ENTROPY = b"diora-tui" def _dpapi(fn_name: str, data: bytes) -> bytes: import ctypes import ctypes.wintypes as wt class Blob(ctypes.Structure): _fields_ = [("cbData", wt.DWORD), ("pbData", ctypes.POINTER(ctypes.c_char))] def blob(b: bytes): buf = ctypes.create_string_buffer(b, len(b)) return Blob(len(b), ctypes.cast(buf, ctypes.POINTER(ctypes.c_char))), buf inp, _k1 = blob(data) ent, _k2 = blob(_ENTROPY) out = Blob() fn = getattr(ctypes.windll.crypt32, fn_name) if not fn(ctypes.byref(inp), None, ctypes.byref(ent), None, None, 1, ctypes.byref(out)): # 1: no UI raise ctypes.WinError() try: return ctypes.string_at(out.pbData, out.cbData) finally: ctypes.windll.kernel32.LocalFree(out.pbData) def protect(data: bytes) -> str: if sys.platform == "win32": return "dpapi:" + base64.b64encode(_dpapi("CryptProtectData", data)).decode() return "plain:" + base64.b64encode(data).decode() def unprotect(blob: str) -> bytes: kind, _, b64 = blob.partition(":") raw = base64.b64decode(b64) if kind == "dpapi": if sys.platform != "win32": raise ValueError("Zugangsdaten sind mit Windows-DPAPI geschützt und nur dort lesbar") return _dpapi("CryptUnprotectData", raw) if kind == "plain": return raw raise ValueError("unbekanntes Format der gespeicherten Zugangsdaten")