From 885081d4e47ac793579f0cc8f72a68d40fccf34a Mon Sep 17 00:00:00 2001 From: Markus Blöchl Date: Sat, 27 Nov 2021 19:35:24 +0100 Subject: qute-keepassxc: Trigger database unlock when querying credentials --- misc/userscripts/qute-keepassxc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index a128c2c3e..b9c1bfc64 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -160,7 +160,7 @@ class KeepassXC: action = 'test-associate', id = self.id, key = base64.b64encode(self.id_key.public_key.encode()).decode('utf-8') - )) + ), triggerUnlock = 'true') return self.recv_msg()['success'] == 'true' def associate(self): -- cgit v1.2.3-54-g00ecf From f738ca3b0c05b6467e86c6612e8ea832f508423b Mon Sep 17 00:00:00 2001 From: Markus Blöchl Date: Sat, 27 Nov 2021 19:37:07 +0100 Subject: qute-keepassxc: Use rofi to select from multiple matching accounts --- misc/userscripts/qute-keepassxc | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index b9c1bfc64..f5fa2ce26 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -43,6 +43,8 @@ config.bind('', 'spawn --userscript qute-keepassxc --key ABC1234', config.bind('pw', 'spawn --userscript qute-keepassxc --key ABC1234', mode='normal') ``` +To manage multiple accounts you also need [rofi](https://github.com/davatorium/rofi) installed. + # Usage @@ -274,6 +276,30 @@ def connect_to_keepassxc(args): return kp +def select_account(creds): + try: + if len(creds) == 1: + return creds[0] + idx = subprocess.check_output( + ['rofi', '-dmenu', '-format', 'i', '-matching', 'fuzzy', + '-p', 'Search', + '-mesg', 'qute-keepassxc: select an account, please!'], + input=b"\n".join(c['login'].encode('utf-8') for c in creds) + ) + idx = int(idx) + if idx < 0: + return None + return creds[idx] + except subprocess.CalledProcessError: + return None + except FileNotFoundError: + error("rofi not found. Please install rofi to select from multiple credentials") + return creds[0] + except Exception as e: + error(f"Error while picking account: {e}") + return None + + def make_js_code(username, password): return ' '.join(""" function isVisible(elem) { @@ -351,8 +377,11 @@ def main(): if not creds: error('No credentials found') return - # TODO: handle multiple matches - name, pw = creds[0]['login'], creds[0]['password'] + cred = select_account(creds) + if not cred: + error('No credentials selected') + return + name, pw = cred['login'], cred['password'] if name and pw: qute('jseval -q ' + make_js_code(name, pw)) except Exception as e: -- cgit v1.2.3-54-g00ecf From 6c1d6dd2c55299f348dc512fb1943b0afdf7d7cc Mon Sep 17 00:00:00 2001 From: Markus Blöchl Date: Sat, 27 Nov 2021 19:44:17 +0100 Subject: qute-keepassxc: Add support for TOTP --- misc/userscripts/qute-keepassxc | 61 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc index f5fa2ce26..61a6c7bce 100755 --- a/misc/userscripts/qute-keepassxc +++ b/misc/userscripts/qute-keepassxc @@ -67,6 +67,26 @@ Therefore you need to have a public-key-pair readily set up. GPG might then ask for your private-key password whenever you query the database for login credentials. +# TOTP + +This script recently received experimental TOTP support. +To use it, you need to have working TOTP authentication within KeepassXC. +Then call `qute-keepassxc` with the `--totp` flags. + +For example, I have the following line in my `config.py`: + +```python +config.bind('pt', 'spawn --userscript qute-keepassxc --key ABC1234 --totp', mode='normal') +``` + +For now this script will simply insert the TOTP-token into the currently selected +input field, since I have not yet found a reliable way to identify the correct field +within all existing login forms. +Thus you need to manually select the TOTP input field, press escape to leave input +mode and then enter `pt` to fill in the token (or configure another key-binding for +insert mode if you prefer that). + + [1]: https://keepassxc.org/ [2]: https://qutebrowser.org/ [3]: https://gnupg.org/ @@ -90,6 +110,8 @@ import nacl.public def parse_args(): parser = argparse.ArgumentParser(description="Full passwords from KeepassXC") parser.add_argument('url', nargs='?', default=os.environ.get('QUTE_URL')) + parser.add_argument('--totp', action='store_true', + help="Fill in current TOTP field instead of username/password") parser.add_argument('--socket', '-s', default='/run/user/{}/org.keepassxc.KeePassXC.BrowserServer'.format(os.getuid()), help='Path to KeepassXC browser socket') parser.add_argument('--key', '-k', default='alice@example.com', @@ -182,6 +204,16 @@ class KeepassXC: )) return self.recv_msg()['entries'] + def get_totp(self, uuid): + self.send_msg(dict( + action = 'get-totp', + uuid = uuid + )) + response = self.recv_msg() + if response['success'] != 'true' or not response['totp']: + return None + return response['totp'] + def send_raw_msg(self, msg): self.sock.send( json.dumps(msg).encode('utf-8') ) @@ -361,6 +393,21 @@ def make_js_code(username, password): """.splitlines()) % (json.dumps(username), json.dumps(password)) +def make_js_totp_code(totp): + return ' '.join(""" + (function () { + var input = document.activeElement; + if (!input || input.tagName !== "INPUT") { + alert("No TOTP input field selected"); + return; + } + input.value = %s; + input.dispatchEvent(new Event('input', { 'bubbles': true })); + input.dispatchEvent(new Event('change', { 'bubbles': true })); + })(); + """.splitlines()) % (json.dumps(totp),) + + def main(): if 'QUTE_FIFO' not in os.environ: print(f"No QUTE_FIFO found - {sys.argv[0]} must be run as a qutebrowser userscript") @@ -381,9 +428,17 @@ def main(): if not cred: error('No credentials selected') return - name, pw = cred['login'], cred['password'] - if name and pw: - qute('jseval -q ' + make_js_code(name, pw)) + if args.totp: + uuid = cred['uuid'] + totp = kp.get_totp(uuid) + if not totp: + error('No TOTP key found') + return + qute('jseval -q ' + make_js_totp_code(totp)) + else: + name, pw = cred['login'], cred['password'] + if name and pw: + qute('jseval -q ' + make_js_code(name, pw)) except Exception as e: error(str(e)) -- cgit v1.2.3-54-g00ecf