56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
|
|
"""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")
|