- commit
- 492599030df9af81ce8bb585c9fac4188f3da221
- parent
- 88faaf1ead8d2e57d79e54de612bd494848872f6
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-05-24 08:08
breaking: store key in separate file See https://github.com/xi/xi-keyring/pull/5 for rationale To migrate, follow these steps: - python3 -m xikeyring --dump > backup.json - update to the new version - python3 -m xikeyring --restore < backup.json - verify that everything works as expected - rm backup.json - rm ~/.local/share/xikeyring.db
Diffstat
| M | xikeyring/__main__.py | 18 | +++++++++++++----- |
| M | xikeyring/keyring.py | 57 | ++++++++++++++++++++++++++++++++++++--------------------- |
2 files changed, 49 insertions, 26 deletions
diff --git a/xikeyring/__main__.py b/xikeyring/__main__.py
@@ -3,7 +3,8 @@ import os 3 3 import sys 4 4 from pathlib import Path 5 56 -1 from . import crypto-1 6 from cryptography.fernet import Fernet -1 7 7 8 from .dbus import DBusService 8 9 from .dumpable import pr_set 9 10 from .keyring import KeyringProxy @@ -35,7 +36,14 @@ def parse_args(): 35 36 '-s', 36 37 help='path to the store file', 37 38 type=Path,38 -1 default=get_data_home() / 'xikeyring.db',-1 39 default=get_data_home() / 'xikeyring' / 'store', -1 40 ) -1 41 parser.add_argument( -1 42 '--key', -1 43 '-k', -1 44 help='path to the key file', -1 45 type=Path, -1 46 default=get_data_home() / 'xikeyring' / 'key', 39 47 ) 40 48 parser.add_argument( 41 49 '--bus', '-b', help='bus name', default='org.freedesktop.secrets' @@ -46,14 +54,14 @@ def parse_args(): 46 54 pr_set(dumpable=False) 47 55 48 56 args = parse_args()49 -1 keyring = KeyringProxy(args.store)-1 57 keyring = KeyringProxy(args.store, args.key) 50 58 if args.dump: 51 59 encrypted = keyring.path.read_bytes()52 -1 decrypted = crypto.decrypt_with_password(encrypted, keyring.password.value)-1 60 decrypted = Fernet(keyring.key.value).decrypt(encrypted) 53 61 print(decrypted.decode('utf-8')) 54 62 elif args.restore: 55 63 decrypted = sys.stdin.read().encode('utf-8')56 -1 encrypted = crypto.encrypt_with_password(decrypted, keyring.password.value)-1 64 encrypted = Fernet(keyring.key.value).encrypt(decrypted) 57 65 write_bytes(keyring.path, encrypted) 58 66 else: 59 67 service = DBusService(keyring)
diff --git a/xikeyring/keyring.py b/xikeyring/keyring.py
@@ -4,6 +4,7 @@ import os 4 4 from dataclasses import dataclass 5 5 from pathlib import Path 6 6 -1 7 from cryptography.fernet import Fernet 7 8 from cryptography.fernet import InvalidToken 8 9 9 10 from . import crypto @@ -40,34 +41,48 @@ def write_bytes(path: Path, data: bytes) -> int: 40 41 41 42 42 43 class Keyring:43 -1 def __init__(self, path: Path):44 -1 self.path = path-1 44 def __init__(self, store_path: Path, key_path: Path): -1 45 self.path = store_path 45 46 self.prompt = Prompt() 46 4747 -1 if self.path.exists():48 -1 while True:49 -1 self.password = self._get_password()50 -1 try:51 -1 self._read()52 -1 break53 -1 except InvalidToken:54 -1 pass-1 48 if key_path.exists(): -1 49 self.key = self._get_key(key_path) 55 50 else:56 -1 self.password = self._get_password()57 -1 self._write({})-1 51 self.key = self._create_key(key_path) 58 5259 -1 def _get_password(self):60 -1 # TODO: different messages for create|unlock|retry-1 53 def _get_key(self, path: Path) -> KernelKey: -1 54 encrypted = path.read_bytes() -1 55 while True: -1 56 password = self.prompt.get_password( -1 57 'An application wants access to your keyring, but it is locked.' -1 58 ) -1 59 if not password: -1 60 raise AccessDeniedError -1 61 try: -1 62 key = crypto.decrypt_with_password(encrypted, password) -1 63 return KernelKey(key) -1 64 except InvalidToken: -1 65 pass -1 66 -1 67 def _create_key(self, path: Path) -> KernelKey: 61 68 password = self.prompt.get_password(62 -1 'An application wants access to your keyring, but it is locked'-1 69 'An application wants access to your keyring. ' -1 70 'Please enter a password to create a keyring.' 63 71 ) 64 72 if not password: 65 73 raise AccessDeniedError66 -1 return KernelKey(password)-1 74 key = Fernet.generate_key() -1 75 encrypted = crypto.encrypt_with_password(key, password) -1 76 path.parent.mkdir(mode=0o700, parents=True, exist_ok=True) -1 77 write_bytes(path, encrypted) -1 78 return KernelKey(key) 67 79 68 80 def _read(self) -> dict[int, Item]: -1 81 if not self.path.exists(): -1 82 return {} -1 83 69 84 encrypted = self.path.read_bytes()70 -1 decrypted = crypto.decrypt_with_password(encrypted, self.password.value)-1 85 decrypted = Fernet(self.key.value).decrypt(encrypted) 71 86 raw = json.loads(decrypted) 72 87 return { 73 88 id: Item(base64.urlsafe_b64decode(secret), attributes, app_id) @@ -85,7 +100,7 @@ class Keyring: 85 100 for id, item in items.items() 86 101 ] 87 102 decrypted = json.dumps(raw).encode('utf-8')88 -1 encrypted = crypto.encrypt_with_password(decrypted, self.password.value)-1 103 encrypted = Fernet(self.key.value).encrypt(decrypted) 89 104 write_bytes(self.path, encrypted) 90 105 91 106 def confirm_access(self, app_id: str) -> None: @@ -157,8 +172,8 @@ class Keyring: 157 172 158 173 159 174 class KeyringProxy:160 -1 def __init__(self, path: Path):161 -1 self.path = path-1 175 def __init__(self, *args): -1 176 self.args = args 162 177 self.keyring = None 163 178 164 179 def lock(self): @@ -166,5 +181,5 @@ class KeyringProxy: 166 181 167 182 def __getattr__(self, attr): 168 183 if self.keyring is None:169 -1 self.keyring = Keyring(self.path)-1 184 self.keyring = Keyring(*self.args) 170 185 return getattr(self.keyring, attr)