xi-keyring

simple and extensible alternative for gnome-keyring
git clone https://git.ce9e.org/xi-keyring.git

commit
07ef4b1db7f2d4b5c8204babbf8e59d27ac3642f
parent
25cfb6114c00dbe07be55fc144d6513d6470f5b6
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-05-10 10:51
only load secrets into memory on requests

Diffstat

M xikeyring/keyring.py 50 +++++++++++++++++++++++++++-----------------------

1 files changed, 27 insertions, 23 deletions


diff --git a/xikeyring/keyring.py b/xikeyring/keyring.py

@@ -79,8 +79,6 @@ class Crypt:
   79    79 
   80    80 
   81    81 class Keyring:
   82    -1     items: dict[int, Item]
   83    -1 
   84    82     def __init__(self, path: str):
   85    83         self.path = path
   86    84         self.prompt = Prompt()
@@ -94,9 +92,8 @@ class Keyring:
   94    92                 except InvalidToken:
   95    93                     pass
   96    94         else:
   97    -1             self.items = {}
   98    95             self.crypt = self._get_crypt()
   99    -1             self._write()
   -1    96             self._write({})
  100    97             os.chmod(self.path, 0o600)
  101    98 
  102    99     def _get_crypt(self):
@@ -108,17 +105,17 @@ class Keyring:
  108   105             raise AccessDeniedError
  109   106         return Crypt(password)
  110   107 
  111    -1     def _read(self):
   -1   108     def _read(self) -> dict[int, Item]:
  112   109         with open(self.path, 'rb') as fh:
  113   110             encrypted = fh.read()
  114   111         decrypted = self.crypt.decrypt(encrypted)
  115   112         raw = json.loads(decrypted)
  116    -1         self.items = {
   -1   113         return {
  117   114             id: Item(base64.urlsafe_b64decode(secret), attributes, exe)
  118   115             for id, secret, attributes, exe in raw
  119   116         }
  120   117 
  121    -1     def _write(self):
   -1   118     def _write(self, items: dict[int, Item]):
  122   119         raw = [
  123   120             (
  124   121                 id,
@@ -126,7 +123,7 @@ class Keyring:
  126   123                 item.attributes,
  127   124                 item.exe,
  128   125             )
  129    -1             for id, item in self.items.items()
   -1   126             for id, item in items.items()
  130   127         ]
  131   128         decrypted = json.dumps(raw).encode('utf-8')
  132   129         encrypted = self.crypt.encrypt(decrypted)
@@ -144,9 +141,9 @@ class Keyring:
  144   141     def has_access(self, exe: str, item: Item) -> bool:
  145   142         return item.exe == exe or exe in TRUSTED_MANAGERS
  146   143 
  147    -1     def get(self, exe: str, id: int) -> Item:
   -1   144     def get(self, items: dict[int, Item], exe: str, id: int) -> Item:
  148   145         try:
  149    -1             item = self.items[id]
   -1   146             item = items[id]
  150   147         except KeyError as e:
  151   148             raise NotFoundError from e
  152   149         if not self.has_access(exe, item):
@@ -154,44 +151,51 @@ class Keyring:
  154   151         return item
  155   152 
  156   153     def search_items(self, exe: str, query: dict[str, str] = {}) -> list[int]:
   -1   154         items = self._read()
  157   155         return [
  158    -1             id for id, item in self.items.items()
   -1   156             id for id, item in items.items()
  159   157             if self.has_access(exe, item) and all(
  160   158                 item.attributes.get(key) == value for key, value in query.items()
  161   159             )
  162   160         ]
  163   161 
  164   162     def get_attributes(self, exe: str, id: int) -> dict[str, str]:
  165    -1         return self.get(exe, id).attributes
   -1   163         items = self._read()
   -1   164         return self.get(items, exe, id).attributes
  166   165 
  167   166     def get_secret(self, exe: str, id: int) -> bytes:
  168    -1         item = self.get(exe, id)
   -1   167         items = self._read()
   -1   168         item = self.get(items, exe, id)
  169   169         self.confirm_access(exe)
  170   170         return item.secret
  171   171 
  172   172     def create_item(self, exe: str, attributes: dict[str, str], secret: bytes) -> int:
  173    -1         id = max(self.items.keys(), default=0) + 1
  174    -1         self.items[id] = Item(secret, attributes, exe)
  175    -1         self._write()
   -1   173         items = self._read()
   -1   174         id = max(items.keys(), default=0) + 1
   -1   175         items[id] = Item(secret, attributes, exe)
   -1   176         self._write(items)
  176   177         return id
  177   178 
  178   179     def update_attributes(self, exe: str, id: int, attributes: dict[str, str]) -> None:
  179    -1         item = self.get(exe, id)
   -1   180         items = self._read()
   -1   181         item = self.get(items, exe, id)
  180   182         self.confirm_change(exe)
  181   183         item.attributes = attributes
  182    -1         self._write()
   -1   184         self._write(items)
  183   185 
  184   186     def update_secret(self, exe: str, id: int, secret: bytes) -> None:
  185    -1         item = self.get(exe, id)
   -1   187         items = self._read()
   -1   188         item = self.get(items, exe, id)
  186   189         self.confirm_change(exe)
  187   190         item.secret = secret
  188    -1         self._write()
   -1   191         self._write(items)
  189   192 
  190   193     def delete_item(self, exe: str, id: int) -> None:
  191    -1         self.get(exe, id)  # trigger appropriate exceptions
   -1   194         items = self._read()
   -1   195         self.get(items, exe, id)  # trigger appropriate exceptions
  192   196         self.confirm_change(exe)
  193    -1         del self.items[id]
  194    -1         self._write()
   -1   197         del items[id]
   -1   198         self._write(items)
  195   199 
  196   200 
  197   201 class KeyringProxy: