Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Replacement keyring backend that uses DPAPI instead of credsmanager
import typing
from contextlib import suppress
from pathlib import Path
import logging
from logextension import LoggingContext
with LoggingContext(logging.getLogger("keyring.backend"), logging.ERROR):
import keyring.backend
KEY_EXT = ".key"
KEY_DIR = "AppKeys"
from src.osutil import is_windows, os_hide_file, os_change_readonly, os_unhide_file
if is_windows():
from win32crypt import CryptProtectData, CryptUnprotectData # pylint: disable=import-error
from win32com.shell import shellcon, shell # pylint: disable=import-error, no-name-in-module
else:
shellcon: typing.Any = None
shell: typing.Any = None
CryptProtectData: typing.Any = None
CryptUnprotectData: typing.Any = None
def windows_get_local_appdata():
# todo: this is a more secure way to get the appdata path
return shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, 0, 0)
def windows_get_roaming_appdata():
# todo: this is a more secure way to get the appdata path
return shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)
def windows_key_path(app: str, user: str) -> Path:
appd = Path(windows_get_local_appdata())
keyd = appd / KEY_DIR
if not keyd.exists():
keyd.mkdir(exist_ok=True)
os_hide_file(keyd)
return keyd / (app + "." + user + KEY_EXT)
def windows_load_key(app: str, user: str):
# todo: SafeMem
path = windows_key_path(app, user)
try:
with path.open("rb") as fh:
(_descr, bkey) = CryptUnprotectData(fh.read())
if bkey and bkey[-1] == 0:
bkey = bkey[:-1]
return str(bkey, "utf8")
except FileNotFoundError:
return None
def windows_save_key(app: str, user: str, key: str):
# todo: SafeMem
path = windows_key_path(app, user)
if path.exists():
# this is used by workstation recovery
os_unhide_file(path)
os_change_readonly(path, False)
with path.open("wb") as fh:
fh.write(CryptProtectData(bytes(key, "utf8")))
os_hide_file(path)
os_change_readonly(path, True)
def windows_delete_key(app: str, user: str):
path = windows_key_path(app, user)
os_change_readonly(path, False)
with suppress(FileNotFoundError):
Path(path).unlink()
class WinKeyring(keyring.backend.KeyringBackend):
"""Replacement keyring backend that uses DPAPI instead of credsmanager"""
def set_password(self, service, username, password):
windows_save_key(service, username, password)
def get_password(self, service, username):
return windows_load_key(service, username)
def delete_password(self, service, username):
return windows_delete_key(service, username)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment