xi-keyring

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

commit
e220d139dd2ecf3626fbac47f8d060c3903cc087
parent
362344710134583725d38b47ab5453dc703e333d
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-07-27 22:00
refactor: rename exe to app_id

Diffstat

M xikeyring/dbus.py 62 ++++++++++++++++++++++++++++++------------------------------
M xikeyring/keyring.py 60 ++++++++++++++++++++++++++++++------------------------------

2 files changed, 61 insertions, 61 deletions


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

@@ -111,7 +111,7 @@ class BaseDBusService:
  111   111         self._call(conn, sender, path, iface, f'Set{prop}', [value], error)
  112   112         return True
  113   113 
  114    -1     def get_exe(self, conn, sender) -> str:
   -1   114     def get_app_id(self, conn, sender) -> str:
  115   115         pid = conn.call_sync(
  116   116             'org.freedesktop.DBus',
  117   117             '/org/freedesktop/DBus',
@@ -182,8 +182,8 @@ class DBusService(BaseDBusService):
  182   182                 ),
  183   183             )
  184   184 
  185    -1     def search_items(self, exe, conn, query={}):
  186    -1         items = self.keyring.search_items(exe, query)
   -1   185     def search_items(self, app_id, conn, query={}):
   -1   186         items = self.keyring.search_items(app_id, query)
  187   187         self.update_items(conn, add=items, emit=False)
  188   188         return items
  189   189 
@@ -228,8 +228,8 @@ class DBusService(BaseDBusService):
  228   228                 self.session_close(conn, sender, path)
  229   229 
  230   230     def service_search_items(self, conn, sender, path, query):
  231    -1         exe = self.get_exe(conn, sender)
  232    -1         items = self.search_items(exe, conn, query)
   -1   231         app_id = self.get_app_id(conn, sender)
   -1   232         items = self.search_items(app_id, conn, query)
  233   233         return GLib.Variant('(aoao)', (self.ids_to_paths(items), []))
  234   234 
  235   235     def service_unlock(self, conn, sender, path, objects):
@@ -241,11 +241,11 @@ class DBusService(BaseDBusService):
  241   241 
  242   242     def service_get_secrets(self, conn, sender, path, items, session_path):
  243   243         session = self.sessions[session_path][2]
  244    -1         exe = self.get_exe(conn, sender)
   -1   244         app_id = self.get_app_id(conn, sender)
  245   245         result = []
  246   246         for path in items:
  247   247             id = int(path.rsplit('/', 1)[1], 10)
  248    -1             secret = self.keyring.get_secret(exe, id)
   -1   248             secret = self.keyring.get_secret(app_id, id)
  249   249             secret_tuple = session.encode(session_path, secret)
  250   250             result.append((path, secret_tuple))
  251   251         return GLib.Variant('(a{o(oayays)})', [result])
@@ -260,8 +260,8 @@ class DBusService(BaseDBusService):
  260   260         return GLib.Variant('ao', [f'{OFSP}/collection/it'])
  261   261 
  262   262     def collection_search_items(self, conn, sender, path, query):
  263    -1         exe = self.get_exe(conn, sender)
  264    -1         items = self.search_items(exe, conn, query)
   -1   263         app_id = self.get_app_id(conn, sender)
   -1   264         items = self.search_items(app_id, conn, query)
  265   265         return GLib.Variant('(ao)', [self.ids_to_paths(items)])
  266   266 
  267   267     def collection_create_item(
@@ -270,21 +270,21 @@ class DBusService(BaseDBusService):
  270   270         session = self.sessions[secret_tuple[0]][2]
  271   271         secret = session.decode(secret_tuple)
  272   272         attributes = properties.get(f'{OFSI}.Item.Attributes', {})
  273    -1         exe = self.get_exe(conn, sender)
   -1   273         app_id = self.get_app_id(conn, sender)
  274   274         id = None
  275   275         if replace:
  276    -1             matches = self.search_items(exe, conn, attributes)
   -1   276             matches = self.search_items(app_id, conn, attributes)
  277   277             if matches:
  278   278                 id = matches[0]
  279    -1                 self.keyring.update_secret(exe, id, secret)
   -1   279                 self.keyring.update_secret(app_id, id, secret)
  280   280         if not id:
  281    -1             id = self.keyring.create_item(exe, attributes, secret)
   -1   281             id = self.keyring.create_item(app_id, attributes, secret)
  282   282             self.update_items(conn, add=[id])
  283   283         return GLib.Variant('(oo)', (f'{OFSP}/collection/it/{id}', '/'))
  284   284 
  285   285     def collection_get_items(self, conn, sender, path):
  286    -1         exe = self.get_exe(conn, sender)
  287    -1         items = self.search_items(exe, conn)
   -1   286         app_id = self.get_app_id(conn, sender)
   -1   287         items = self.search_items(app_id, conn)
  288   288         return GLib.Variant('ao', self.ids_to_paths(items))
  289   289 
  290   290     def collection_get_label(self, conn, sender, path):
@@ -301,15 +301,15 @@ class DBusService(BaseDBusService):
  301   301 
  302   302     def item_delete(self, conn, sender, path):
  303   303         id = int(path.rsplit('/', 1)[1], 10)
  304    -1         exe = self.get_exe(conn, sender)
  305    -1         self.keyring.delete_item(exe, id)
   -1   304         app_id = self.get_app_id(conn, sender)
   -1   305         self.keyring.delete_item(app_id, id)
  306   306         self.update_items(conn, rm=[id])
  307   307         return GLib.Variant('(o)', ['/'])
  308   308 
  309   309     def item_get_secret(self, conn, sender, path, session_path):
  310   310         id = int(path.rsplit('/', 1)[1], 10)
  311    -1         exe = self.get_exe(conn, sender)
  312    -1         secret = self.keyring.get_secret(exe, id)
   -1   311         app_id = self.get_app_id(conn, sender)
   -1   312         secret = self.keyring.get_secret(app_id, id)
  313   313         session = self.sessions[session_path][2]
  314   314         secret_tuple = session.encode(session_path, secret)
  315   315         return GLib.Variant('((oayays))', [secret_tuple])
@@ -318,8 +318,8 @@ class DBusService(BaseDBusService):
  318   318         id = int(path.rsplit('/', 1)[1], 10)
  319   319         session = self.sessions[secret_tuple[0]][2]
  320   320         secret = session.decode(secret_tuple)
  321    -1         exe = self.get_exe(conn, sender)
  322    -1         self.keyring.update_secret(exe, id, secret)
   -1   321         app_id = self.get_app_id(conn, sender)
   -1   322         self.keyring.update_secret(app_id, id, secret)
  323   323 
  324   324     def item_get_label(self, conn, sender, path):
  325   325         return GLib.Variant('s', path.rsplit('/', 1)[1])
@@ -338,15 +338,15 @@ class DBusService(BaseDBusService):
  338   338 
  339   339     def item_get_attributes(self, conn, sender, path):
  340   340         id = int(path.rsplit('/', 1)[1], 10)
  341    -1         exe = self.get_exe(conn, sender)
  342    -1         attributes = self.keyring.get_attributes(exe, id)
   -1   341         app_id = self.get_app_id(conn, sender)
   -1   342         attributes = self.keyring.get_attributes(app_id, id)
  343   343         return GLib.Variant('a{ss}', attributes.items())
  344   344 
  345   345     def item_set_attributes(self, conn, sender, path, value):
  346   346         id = int(path.rsplit('/', 1)[1], 10)
  347   347         attributes = value.unpack()
  348    -1         exe = self.get_exe(conn, sender)
  349    -1         self.keyring.update_attributes(exe, id, attributes)
   -1   348         app_id = self.get_app_id(conn, sender)
   -1   349         self.keyring.update_attributes(app_id, id, attributes)
  350   350 
  351   351         conn.emit_signal(
  352   352             None,
@@ -372,20 +372,20 @@ class DBusService(BaseDBusService):
  372   372     def secret_get_version(self, conn, sender, path):
  373   373         return GLib.Variant('u', 1)
  374   374 
  375    -1     def secret_retrieve_secret(self, conn, sender, path, handle, app_id, fd, options):
   -1   375     def secret_retrieve_secret(self, conn, sender, path, handle, client_app_id, fd, options):
  376   376         reg_id = self.register_object(conn, handle, 'org.freedesktop.impl.portal.Request')
  377   377         try:
  378    -1             exe = self.get_exe(conn, sender)
   -1   378             app_id = self.get_app_id(conn, sender)
  379   379             attrs = {
  380   380                 'application': 'org.freedesktop.portal.Secret',
  381    -1                 'app_id': app_id,
   -1   381                 'app_id': client_app_id,
  382   382             }
  383    -1             ids = self.keyring.search_items(exe, attrs)
   -1   383             ids = self.keyring.search_items(app_id, attrs)
  384   384             if ids:
  385    -1                 secret = self.keyring.get_secret(exe, ids[0])
   -1   385                 secret = self.keyring.get_secret(app_id, ids[0])
  386   386             else:
  387   387                 secret = os.urandom(64)
  388    -1                 self.keyring.create_item(exe, attrs, secret)
   -1   388                 self.keyring.create_item(app_id, attrs, secret)
  389   389             os.write(fd, secret)
  390   390         finally:
  391   391             conn.unregister_object(reg_id)

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

@@ -28,7 +28,7 @@ class NotFoundError(Exception):
   28    28 class Item:
   29    29     secret: bytes
   30    30     attributes: dict[str, str]
   31    -1     exe: str
   -1    31     app_id: str
   32    32 
   33    33 
   34    34 class Crypt:
@@ -111,8 +111,8 @@ class Keyring:
  111   111         decrypted = self.crypt.decrypt(encrypted)
  112   112         raw = json.loads(decrypted)
  113   113         return {
  114    -1             id: Item(base64.urlsafe_b64decode(secret), attributes, exe)
  115    -1             for id, secret, attributes, exe in raw
   -1   114             id: Item(base64.urlsafe_b64decode(secret), attributes, app_id)
   -1   115             for id, secret, attributes, app_id in raw
  116   116         }
  117   117 
  118   118     def _write(self, items: dict[int, Item]):
@@ -121,7 +121,7 @@ class Keyring:
  121   121                 id,
  122   122                 base64.urlsafe_b64encode(item.secret).decode(),
  123   123                 item.attributes,
  124    -1                 item.exe,
   -1   124                 item.app_id,
  125   125             )
  126   126             for id, item in items.items()
  127   127         ]
@@ -130,70 +130,70 @@ class Keyring:
  130   130         with open(self.path, 'wb') as fh:
  131   131             fh.write(encrypted)
  132   132 
  133    -1     def confirm_access(self, exe: str) -> None:
  134    -1         if not self.prompt.confirm(f'Allow {exe} to access a secret from your keyring?'):
   -1   133     def confirm_access(self, app_id: str) -> None:
   -1   134         if not self.prompt.confirm(f'Allow {app_id} to access a secret from your keyring?'):
  135   135             raise AccessDeniedError
  136   136 
  137    -1     def confirm_change(self, exe: str) -> None:
  138    -1         if not self.prompt.confirm(f'Allow {exe} to make changes to your keyring?'):
   -1   137     def confirm_change(self, app_id: str) -> None:
   -1   138         if not self.prompt.confirm(f'Allow {app_id} to make changes to your keyring?'):
  139   139             raise AccessDeniedError
  140   140 
  141    -1     def has_access(self, exe: str, item: Item) -> bool:
  142    -1         return item.exe == exe or exe in TRUSTED_MANAGERS
   -1   141     def has_access(self, app_id: str, item: Item) -> bool:
   -1   142         return item.app_id == app_id or app_id in TRUSTED_MANAGERS
  143   143 
  144    -1     def get(self, items: dict[int, Item], exe: str, id: int) -> Item:
   -1   144     def get(self, items: dict[int, Item], app_id: str, id: int) -> Item:
  145   145         try:
  146   146             item = items[id]
  147   147         except KeyError as e:
  148   148             raise NotFoundError from e
  149    -1         if not self.has_access(exe, item):
   -1   149         if not self.has_access(app_id, item):
  150   150             raise NotFoundError
  151   151         return item
  152   152 
  153    -1     def search_items(self, exe: str, query: dict[str, str] = {}) -> list[int]:
   -1   153     def search_items(self, app_id: str, query: dict[str, str] = {}) -> list[int]:
  154   154         items = self._read()
  155   155         return [
  156   156             id for id, item in items.items()
  157    -1             if self.has_access(exe, item) and all(
   -1   157             if self.has_access(app_id, item) and all(
  158   158                 item.attributes.get(key) == value for key, value in query.items()
  159   159             )
  160   160         ]
  161   161 
  162    -1     def get_attributes(self, exe: str, id: int) -> dict[str, str]:
   -1   162     def get_attributes(self, app_id: str, id: int) -> dict[str, str]:
  163   163         items = self._read()
  164    -1         return self.get(items, exe, id).attributes
   -1   164         return self.get(items, app_id, id).attributes
  165   165 
  166    -1     def get_secret(self, exe: str, id: int) -> bytes:
   -1   166     def get_secret(self, app_id: str, id: int) -> bytes:
  167   167         items = self._read()
  168    -1         item = self.get(items, exe, id)
  169    -1         self.confirm_access(exe)
   -1   168         item = self.get(items, app_id, id)
   -1   169         self.confirm_access(app_id)
  170   170         return item.secret
  171   171 
  172    -1     def create_item(self, exe: str, attributes: dict[str, str], secret: bytes) -> int:
   -1   172     def create_item(self, app_id: str, attributes: dict[str, str], secret: bytes) -> int:
  173   173         items = self._read()
  174   174         id = max(items.keys(), default=0) + 1
  175    -1         items[id] = Item(secret, attributes, exe)
   -1   175         items[id] = Item(secret, attributes, app_id)
  176   176         self._write(items)
  177   177         return id
  178   178 
  179    -1     def update_attributes(self, exe: str, id: int, attributes: dict[str, str]) -> None:
   -1   179     def update_attributes(self, app_id: str, id: int, attributes: dict[str, str]) -> None:
  180   180         items = self._read()
  181    -1         item = self.get(items, exe, id)
  182    -1         self.confirm_change(exe)
   -1   181         item = self.get(items, app_id, id)
   -1   182         self.confirm_change(app_id)
  183   183         item.attributes = attributes
  184   184         self._write(items)
  185   185 
  186    -1     def update_secret(self, exe: str, id: int, secret: bytes) -> None:
   -1   186     def update_secret(self, app_id: str, id: int, secret: bytes) -> None:
  187   187         items = self._read()
  188    -1         item = self.get(items, exe, id)
  189    -1         self.confirm_change(exe)
   -1   188         item = self.get(items, app_id, id)
   -1   189         self.confirm_change(app_id)
  190   190         item.secret = secret
  191   191         self._write(items)
  192   192 
  193    -1     def delete_item(self, exe: str, id: int) -> None:
   -1   193     def delete_item(self, app_id: str, id: int) -> None:
  194   194         items = self._read()
  195    -1         self.get(items, exe, id)  # trigger appropriate exceptions
  196    -1         self.confirm_change(exe)
   -1   195         self.get(items, app_id, id)  # trigger appropriate exceptions
   -1   196         self.confirm_change(app_id)
  197   197         del items[id]
  198   198         self._write(items)
  199   199