diff --git a/README.md b/README.md index 34a9f22..c84dc97 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # sshyp-labs Experimental extensions for the sshyp password manager. + +# Acknowledgements +sshyp-mfa relies on [ValvePython/steam](https://github.com/ValvePython/steam) for Steam support. + +sshyp-mfa's TOTP support is partially derrived from [susam/mintotp](https://github.com/susam/mintotp). diff --git a/sshyp-mfa/bin/sshyp-mfa.py b/sshyp-mfa/bin/sshyp-mfa.py new file mode 100755 index 0000000..0f8d527 --- /dev/null +++ b/sshyp-mfa/bin/sshyp-mfa.py @@ -0,0 +1,76 @@ +#!/bin/python3 + +from base64 import b32decode +from hmac import new as new_mac +from os import environ, path, system, uname +from pathlib import Path +from shutil import rmtree +from sshyp import decrypt, entry_list_gen, shm_gen +from sshync import get_profile +from steam.guard import generate_twofactor_code as steam_totp +from struct import pack, unpack +from sys import argv, exit as s_exit +from time import sleep, time + + +def totp(_secret, _algo, _digits, _period): + _secret = b32decode(_secret.upper() + '=' * ((8 - len(_secret)) % 8)) + _counter = pack('>Q', int(time() / _period)) + _mac = new_mac(_secret, _counter, _algo).digest() + _offset = _mac[-1] & 0x0f + _binary = unpack('>L', _mac[_offset:_offset + 4])[0] & 0x7fffffff + return str(_binary)[-_digits:].zfill(_digits) + + +def mfa_read_shortcut(): + if not Path(f"{directory}{argument_list[1].replace('/', '', 1)}.gpg").exists(): + print(f"\n\u001b[38;5;9merror: entry ({argument_list[1].replace('/', '', 1)}) does not exist\u001b[0m\n") + s_exit(1) + _shm_folder, _shm_entry = shm_gen() + decrypt(directory + argument_list[1].replace('/', '', 1), _shm_folder, _shm_entry, gpg) + _mfa_data = open(f"{path.expanduser('~/.config/sshyp/tmp/')}{_shm_folder}/{_shm_entry}", 'r').readlines() + _type = _mfa_data[4].split('otpauth://')[1].split('/')[0] + _secret = _mfa_data[4].split('?secret=')[1].split('&issuer=')[0] + _algo = _mfa_data[4].split('&algorithm=')[1].split('&digits=')[0] + _digits = int(_mfa_data[4].split('&digits=')[1].split('&period=')[0]) + _period = int(_mfa_data[4].split('&period=')[1]) + rmtree(f"{path.expanduser('~/.config/sshyp/tmp/')}{_shm_folder}") + return _type, _secret, _algo, _digits, _period + + +if __name__ == '__main__': + # argument fetcher + argument_list = argv + + # user data fetcher + ssh_info = get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync')) + directory = str(ssh_info[3].replace('\n', '')) + if uname()[0] == 'Haiku': # set proper gpg command for OS + gpg = 'gpg --pinentry-mode loopback' + else: + gpg = 'gpg' + + try: + if len(argument_list) == 1: + entry_list_gen() + argument_list.append(input('entry to read: ')) + mfa_data = mfa_read_shortcut() + print('\nmfa key copied to clipboard\n\nuntil this process is closed, your clipboard will be automatically ' + 'updated with the newest mfa key') + while True: + sleep(1) + if mfa_data[0] == 'steam': + _mfa_key = steam_totp(b32decode(mfa_data[1])) + else: + _mfa_key = totp(mfa_data[1], mfa_data[2], mfa_data[3], mfa_data[4]) + if uname()[0] == 'Haiku': # Haiku clipboard detection + system(f"clipboard -c '{_mfa_key}'") + elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection + system(f"termux-clipboard-set '{_mfa_key}'") + elif environ.get('WAYLAND_DISPLAY') == 'wayland-0': # Wayland clipboard detection + system(f"wl-copy '{_mfa_key}'") + else: # X11 clipboard detection + system(f"echo -n '{_mfa_key}' | xclip -sel c") + except KeyboardInterrupt: + print('\n') + s_exit() diff --git a/sshyp-mfa/requirements.txt b/sshyp-mfa/requirements.txt new file mode 100644 index 0000000..51eb0da --- /dev/null +++ b/sshyp-mfa/requirements.txt @@ -0,0 +1 @@ +steam[client] diff --git a/sshyp-mfa/share/licenses/sshyp-mfa/license b/sshyp-mfa/share/licenses/sshyp-mfa/license new file mode 100755 index 0000000..2ec5bd0 --- /dev/null +++ b/sshyp-mfa/share/licenses/sshyp-mfa/license @@ -0,0 +1,15 @@ + sshyp-mfa is a FOSS extension for the sshyp password manager. + Copyright (C) 2022 Randall Winkhart idgr@tutanota.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see .