- commit
- c6af7f9f0a1cbadccb2ce7f70c509304ece03a45
- parent
- 58259b8b0382a52b548bf1799564c1b058eb97b2
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2024-04-05 06:18
restrict access based on sender exe
Diffstat
| M | xikeyring/dbus.py | 53 | +++++++++++++++++++++++++++++++++++++++-------------- |
| M | xikeyring/keyring.py | 71 | ++++++++++++++++++++++++++++++++++++------------------------- |
2 files changed, 81 insertions, 43 deletions
diff --git a/xikeyring/dbus.py b/xikeyring/dbus.py
@@ -1,4 +1,5 @@ 1 1 import logging -1 2 import os 2 3 import re 3 4 import sys 4 5 from pathlib import Path @@ -104,6 +105,20 @@ class BaseDBusService: 104 105 self._call(conn, sender, path, iface, f'Set{prop}', [value], error) 105 106 return True 106 107 -1 108 def get_exe(self, conn, sender) -> str: -1 109 pid = conn.call_sync( -1 110 'org.freedesktop.DBus', -1 111 '/org/freedesktop/DBus', -1 112 'org.freedesktop.DBus', -1 113 'GetConnectionUnixProcessID', -1 114 GLib.Variant('(s)', [sender]), -1 115 GLib.VariantType('(u)'), -1 116 Gio.DBusCallFlags.NONE, -1 117 -1, -1 118 None, -1 119 )[0] -1 120 return os.readlink(f'/proc/{pid}/exe') -1 121 107 122 108 123 class DBusService(BaseDBusService): 109 124 def __init__(self, keyring): @@ -163,8 +178,8 @@ class DBusService(BaseDBusService): 163 178 ), 164 179 ) 165 180166 -1 def search_items(self, conn, query={}, *, emit=True):167 -1 items = self.keyring.search_items(query)-1 181 def search_items(self, exe, conn, query={}, *, emit=True): -1 182 items = self.keyring.search_items(exe, query) 168 183 if query: 169 184 self.update_items(conn, add=items, emit=emit) 170 185 else: @@ -186,7 +201,8 @@ class DBusService(BaseDBusService): 186 201 return GLib.Variant('(vo)', (GLib.Variant('ay', output), session_path)) 187 202 188 203 def service_search_items(self, conn, sender, path, query):189 -1 items = self.search_items(conn, query)-1 204 exe = self.get_exe(conn, sender) -1 205 items = self.search_items(exe, conn, query) 190 206 return GLib.Variant('(aoao)', (self.ids_to_paths(items), [])) 191 207 192 208 def service_unlock(self, conn, sender, path, objects): @@ -198,10 +214,11 @@ class DBusService(BaseDBusService): 198 214 199 215 def service_get_secrets(self, conn, sender, path, items, session_path): 200 216 session = self.sessions[session_path] -1 217 exe = self.get_exe(conn, sender) 201 218 result = [] 202 219 for path in items: 203 220 id = int(path.rsplit('/', 1)[1], 10)204 -1 secret = self.keyring.get_secret(id)-1 221 secret = self.keyring.get_secret(exe, id) 205 222 secret_tuple = session.encode(session_path, secret) 206 223 result.append((path, secret_tuple)) 207 224 return GLib.Variant('(a{o(oayays)})', [result]) @@ -216,7 +233,8 @@ class DBusService(BaseDBusService): 216 233 return GLib.Variant('ao', [f'{OFSP}/collection/it']) 217 234 218 235 def collection_search_items(self, conn, sender, path, query):219 -1 items = self.search_items(conn, query)-1 236 exe = self.get_exe(conn, sender) -1 237 items = self.search_items(exe, conn, query) 220 238 return GLib.Variant('(ao)', [self.ids_to_paths(items)]) 221 239 222 240 def collection_create_item( @@ -225,19 +243,21 @@ class DBusService(BaseDBusService): 225 243 session = self.sessions[secret_tuple[0]] 226 244 secret = session.decode(secret_tuple) 227 245 attributes = properties.get(f'{OFSI}.Item.Attributes', {}) -1 246 exe = self.get_exe(conn, sender) 228 247 id = None 229 248 if replace:230 -1 matches = self.search_items(conn, attributes)-1 249 matches = self.search_items(exe, conn, attributes) 231 250 if matches: 232 251 id = matches[0]233 -1 self.keyring.update_secret(id, secret)-1 252 self.keyring.update_secret(exe, id, secret) 234 253 if not id:235 -1 id = self.keyring.create_item(attributes, secret)-1 254 id = self.keyring.create_item(exe, attributes, secret) 236 255 self.update_items(conn, add=[id]) 237 256 return GLib.Variant('(oo)', (f'{OFSP}/collection/it/{id}', '/')) 238 257 239 258 def collection_get_items(self, conn, sender, path):240 -1 items = self.search_items(conn)-1 259 exe = self.get_exe(conn, sender) -1 260 items = self.search_items(exe, conn) 241 261 return GLib.Variant('ao', self.ids_to_paths(items)) 242 262 243 263 def collection_get_label(self, conn, sender, path): @@ -254,13 +274,15 @@ class DBusService(BaseDBusService): 254 274 255 275 def item_delete(self, conn, sender, path): 256 276 id = int(path.rsplit('/', 1)[1], 10)257 -1 self.keyring.delete_item(id)-1 277 exe = self.get_exe(conn, sender) -1 278 self.keyring.delete_item(exe, id) 258 279 self.update_items(conn, rm=[id]) 259 280 return GLib.Variant('(o)', ['/']) 260 281 261 282 def item_get_secret(self, conn, sender, path, session_path): 262 283 id = int(path.rsplit('/', 1)[1], 10)263 -1 secret = self.keyring.get_secret(id)-1 284 exe = self.get_exe(conn, sender) -1 285 secret = self.keyring.get_secret(exe, id) 264 286 session = self.sessions[session_path] 265 287 secret_tuple = session.encode(session_path, secret) 266 288 return GLib.Variant('((oayays))', [secret_tuple]) @@ -269,7 +291,8 @@ class DBusService(BaseDBusService): 269 291 id = int(path.rsplit('/', 1)[1], 10) 270 292 session = self.sessions[secret_tuple[0]] 271 293 secret = session.decode(secret_tuple)272 -1 self.keyring.update_secret(id, secret)-1 294 exe = self.get_exe(conn, sender) -1 295 self.keyring.update_secret(exe, id, secret) 273 296 274 297 def item_get_label(self, conn, sender, path): 275 298 return GLib.Variant('s', path.rsplit('/', 1)[1]) @@ -288,13 +311,15 @@ class DBusService(BaseDBusService): 288 311 289 312 def item_get_attributes(self, conn, sender, path): 290 313 id = int(path.rsplit('/', 1)[1], 10)291 -1 attributes = self.keyring.get_attributes(id)-1 314 exe = self.get_exe(conn, sender) -1 315 attributes = self.keyring.get_attributes(exe, id) 292 316 return GLib.Variant('a{ss}', attributes.items()) 293 317 294 318 def item_set_attributes(self, conn, sender, path, value): 295 319 id = int(path.rsplit('/', 1)[1], 10) 296 320 attributes = value.unpack()297 -1 self.keyring.update_attributes(id, attributes)-1 321 exe = self.get_exe(conn, sender) -1 322 self.keyring.update_attributes(exe, id, attributes) 298 323 299 324 conn.emit_signal( 300 325 None,
diff --git a/xikeyring/keyring.py b/xikeyring/keyring.py
@@ -10,6 +10,10 @@ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 10 10 11 11 from .prompt import PinentryPrompt as Prompt 12 12 -1 13 TRUSTED_MANAGERS = [ -1 14 '/usr/bin/seahorse', -1 15 ] -1 16 13 17 14 18 class AccessDeniedError(Exception): 15 19 pass @@ -23,6 +27,7 @@ class NotFoundError(Exception): 23 27 class Item: 24 28 secret: bytes 25 29 attributes: dict[str, str] -1 30 exe: str 26 31 27 32 28 33 class Crypt: @@ -108,8 +113,8 @@ class Keyring: 108 113 decrypted = self.crypt.decrypt(encrypted) 109 114 raw = json.loads(decrypted) 110 115 self.items = {111 -1 id: Item(base64.urlsafe_b64decode(secret), attributes)112 -1 for id, secret, attributes in raw-1 116 id: Item(base64.urlsafe_b64decode(secret), attributes, exe) -1 117 for id, secret, attributes, exe in raw 113 118 } 114 119 115 120 def _write(self): @@ -118,6 +123,7 @@ class Keyring: 118 123 id, 119 124 base64.urlsafe_b64encode(item.secret).decode(), 120 125 item.attributes, -1 126 item.exe, 121 127 ) 122 128 for id, item in self.items.items() 123 129 ] @@ -126,57 +132,64 @@ class Keyring: 126 132 with open(self.path, 'wb') as fh: 127 133 fh.write(encrypted) 128 134129 -1 def confirm_access(self) -> None:130 -1 if not self.prompt.confirm('Allow access to secret from keyring?'):-1 135 def confirm_access(self, exe: str) -> None: -1 136 if not self.prompt.confirm(f'Allow {exe} to access a secret from yout keyring?'): 131 137 raise AccessDeniedError 132 138133 -1 def confirm_change(self) -> None:134 -1 if not self.prompt.confirm('Allow changes to keyring?'):-1 139 def confirm_change(self, exe: str) -> None: -1 140 if not self.prompt.confirm(f'Allow {exe} to make changes to your keyring?'): 135 141 raise AccessDeniedError 136 142137 -1 def __getitem__(self, id: int) -> Item:-1 143 def has_access(self, exe: str, item: Item) -> bool: -1 144 return item.exe == exe or exe in TRUSTED_MANAGERS -1 145 -1 146 def get(self, exe: str, id: int) -> Item: 138 147 try:139 -1 return self.items[id]-1 148 item = self.items[id] 140 149 except KeyError as e: 141 150 raise NotFoundError from e -1 151 if not self.has_access(exe, item): -1 152 raise NotFoundError -1 153 return item 142 154143 -1 def search_items(self, query: dict[str, str] = {}) -> list[int]:-1 155 def search_items(self, exe: str, query: dict[str, str] = {}) -> list[int]: 144 156 return [ 145 157 id for id, item in self.items.items()146 -1 if not query or all(-1 158 if self.has_access(exe, item) and all( 147 159 item.attributes.get(key) == value for key, value in query.items() 148 160 ) 149 161 ] 150 162151 -1 def get_attributes(self, id: int) -> dict[str, str]:152 -1 return self[id].attributes-1 163 def get_attributes(self, exe: str, id: int) -> dict[str, str]: -1 164 return self.get(exe, id).attributes 153 165154 -1 def get_secret(self, id: int) -> bytes:155 -1 self.confirm_access()156 -1 return self[id].secret-1 166 def get_secret(self, exe: str, id: int) -> bytes: -1 167 item = self.get(exe, id) -1 168 self.confirm_access(exe) -1 169 return item.secret 157 170158 -1 def create_item(self, attributes: dict[str, str], secret: bytes) -> int:-1 171 def create_item(self, exe: str, attributes: dict[str, str], secret: bytes) -> int: 159 172 id = max(self.items.keys(), default=0) + 1160 -1 self.items[id] = Item(secret, attributes)-1 173 self.items[id] = Item(secret, attributes, exe) 161 174 self._write() 162 175 return id 163 176164 -1 def update_attributes(self, id: int, attributes: dict[str, str]) -> None:165 -1 self.confirm_change()166 -1 self[id].attributes = attributes-1 177 def update_attributes(self, exe: str, id: int, attributes: dict[str, str]) -> None: -1 178 item = self.get(exe, id) -1 179 self.confirm_change(exe) -1 180 item.attributes = attributes 167 181 self._write() 168 182169 -1 def update_secret(self, id: int, secret: bytes) -> None:170 -1 self.confirm_change()171 -1 self[id].secret = secret-1 183 def update_secret(self, exe: str, id: int, secret: bytes) -> None: -1 184 item = self.get(exe, id) -1 185 self.confirm_change(exe) -1 186 item.secret = secret 172 187 self._write() 173 188174 -1 def delete_item(self, id: int) -> None:175 -1 self.confirm_change()176 -1 try:177 -1 del self.items[id]178 -1 except KeyError as e:179 -1 raise NotFoundError from e-1 189 def delete_item(self, exe: str, id: int) -> None: -1 190 self.get(exe, id) # trigger appropriate exceptions -1 191 self.confirm_change(exe) -1 192 del self.items[id] 180 193 self._write() 181 194 182 195