11 Commits
5 changed files with 46 additions and 26 deletions
+5 -3
View File
@@ -9,11 +9,13 @@ For most sshyp-supported platforms, sshyp extensions should be installed from th
For **Haiku and Termux _ONLY_**, use the packages from the [releases page](https://github.com/rwinkhart/sshyp-labs/releases). For **Haiku and Termux _ONLY_**, use the packages from the [releases page](https://github.com/rwinkhart/sshyp-labs/releases).
# Available Extensions # Available Extensions
[sshyp-mfa](https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa): read mfa data from sshyp entries to generate and copy totp keys to the clipboard (optional Steam support) [sshyp-mfa](https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa): read mfa data from sshyp entries to generate and copy totp keys to the clipboard (includes Steam support)
[password-pasture](https://github.com/rwinkhart/sshyp-labs/wiki/password-pasture): a HIGHLY experimental GTK4 sshyp GUI - very incomplete (last updated for sshyp v1.1.x) [password-pasture](https://github.com/rwinkhart/sshyp-labs/wiki/password-pasture): a HIGHLY experimental GTK4 sshyp GUI - very incomplete (last updated for sshyp v1.1.x)
# Acknowledgements # Acknowledgements
sshyp-mfa optionally depends 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). sshyp-mfa's TOTP support is partially derrived from [susam/mintotp](https://github.com/susam/mintotp).
sshyp-mfa's Steam support is partially derrived from [ValvePython/steam](https://github.com/ValvePython/steam).
These packages are _not_ required as dependencies; the necessary code from each is included in sshyp-mfa.
+4 -4
View File
@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
version='1.5.0.2' version='1.5.1.3'
if [ -z "$2" ]; then if [ -z "$2" ]; then
revision=1 revision=1
else else
@@ -34,8 +34,8 @@ urls {
} }
" > output/haikutemp/.PackageInfo " > output/haikutemp/.PackageInfo
cp ./sshyp-mfa.py output/haikutemp/lib/sshyp/sshyp-mfa cp ./sshyp-mfa.py output/haikutemp/lib/sshyp/sshyp-mfa
cp ./sshyp-mfa.ini output/haikutemp/lib/sshyp/extensions/ printf '[config]\ninput = copy -m\noutput = /system/lib/sshyp/sshyp-mfa\n' > ./output/haikutemp/lib/sshyp/extensions/sshyp-mfa.ini
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshyp-mfa sed -i '1 s/.*/#!\/bin\/env\ python3.11/' output/haikutemp/lib/sshyp/sshyp-mfa
cd output/haikutemp cd output/haikutemp
package create -b HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg package create -b HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg
package add HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg lib package add HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg lib
@@ -60,7 +60,7 @@ Priority: optional
Installed-Size: 100 Installed-Size: 100
" > output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/DEBIAN/control " > output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/DEBIAN/control
cp ./sshyp-mfa.py output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/sshyp-mfa cp ./sshyp-mfa.py output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/sshyp-mfa
cp ./sshyp-mfa.ini output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/extensions/ printf '[config]\ninput = copy -m\noutput = /data/data/com.termux/files/usr/lib/sshyp/sshyp-mfa\n' > ./output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/extensions/sshyp-mfa.ini
dpkg-deb --build --root-owner-group output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/ dpkg-deb --build --root-owner-group output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/
mv output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux.deb output/TERMUX-sshyp-mfa_"$version"-"$revision"_all.deb mv output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux.deb output/TERMUX-sshyp-mfa_"$version"-"$revision"_all.deb
rm -rf output/termuxtemp rm -rf output/termuxtemp
+27 -19
View File
@@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from base64 import b32decode from base64 import b32decode
from configparser import ConfigParser from configparser import ConfigParser
from hmac import new as hmac_new
from os import environ, listdir, uname from os import environ, listdir, uname
from os.path import expanduser, isdir, isfile from os.path import expanduser, isdir, isfile
from sshyp import decrypt, whitelist_verify from sshyp import decrypt, whitelist_verify
from struct import pack, unpack
from subprocess import PIPE, Popen, run from subprocess import PIPE, Popen, run
from sys import argv, exit as s_exit from sys import argv, exit as s_exit
from time import sleep, strftime, time from time import sleep, strftime, time
@@ -11,25 +13,37 @@ home = expanduser("~")
def totp(_secret, _algo, _digits, _period): # uses provided information to generate a standard totp key def totp(_secret, _algo, _digits, _period): # uses provided information to generate a standard totp key
from hmac import new as new_mac
from struct import pack, unpack
_secret = b32decode(_secret.upper() + '=' * ((8 - len(_secret)) % 8)) _secret = b32decode(_secret.upper() + '=' * ((8 - len(_secret)) % 8))
_counter = pack('>Q', int(time() / _period)) _counter = pack('>Q', int(time() / _period))
_mac = new_mac(_secret, _counter, _algo).digest() _hmac = hmac_new(_secret, _counter, _algo).digest()
_offset = _mac[-1] & 0x0f _offset = _hmac[-1] & 0x0f
_binary = unpack('>L', _mac[_offset:_offset + 4])[0] & 0x7fffffff _binary = unpack('>L', _hmac[_offset:_offset + 4])[0] & 0x7fffffff
return str(_binary)[-_digits:].zfill(_digits) return str(_binary)[-_digits:].zfill(_digits)
def steam_otp(_secret): # uses provided information to generate a Steam-compatible otp
_hmac = hmac_new(bytes(_secret), msg=pack('>Q', int(time()//30)), digestmod='sha1').digest()
_start = ord(_hmac[19:20]) & 0xF
_codeint = unpack('>I', _hmac[_start:_start+4])[0] & 0x7fffffff
_charset = '23456789BCDFGHJKMNPQRTVWXY'
_code = ''
for _ in range(5):
_codeint, _i = divmod(_codeint, len(_charset))
_code += _charset[_i]
return _code
def mfa_read_shortcut(): # extracts MFA info from the user-specified sshyp entry def mfa_read_shortcut(): # extracts MFA info from the user-specified sshyp entry
if not isfile(f"{directory}{arguments[0]}.gpg"): if not isfile(f"{directory}{arguments[0]}.gpg"):
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not exist\u001b[0m\n") print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not exist\u001b[0m\n")
s_exit(1) s_exit(1)
if quick_unlock_enabled == 'true': if quick_unlock_enabled:
_mfa_data = decrypt(directory + arguments[0], _mfa_data = decrypt(directory + arguments[0],
_quick_pass=whitelist_verify(sshyp_data.get('SSHYNC', 'port'), _quick_pass=whitelist_verify(sshyp_data.get('SSHYNC', 'port'),
sshyp_data.get('SSHYNC', 'user'), sshyp_data.get('SSHYNC', 'ip'), sshyp_data.get('SSHYNC', 'user'),
listdir(f"{home}/.config/sshyp/devices")[0])) sshyp_data.get('SSHYNC', 'ip'),
listdir(f"{home}/.config/sshyp/devices")[0],
sshyp_data.get('SSHYNC', 'identity_file')))
else: else:
_mfa_data = decrypt(directory + arguments[0]) _mfa_data = decrypt(directory + arguments[0])
try: try:
@@ -55,7 +69,7 @@ if __name__ == '__main__':
sshyp_data = ConfigParser() sshyp_data = ConfigParser()
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini") sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
directory = f"{home}/.local/share/sshyp/" directory = f"{home}/.local/share/sshyp/"
quick_unlock_enabled = sshyp_data.get('CLIENT-ONLINE', 'quick_unlock_enabled') quick_unlock_enabled = sshyp_data.getboolean('CLIENT-ONLINE', 'quick_unlock_enabled')
# main process: runs functions to generate MFA key, then continuously copies up-to-date MFA key to clipboard # main process: runs functions to generate MFA key, then continuously copies up-to-date MFA key to clipboard
try: try:
@@ -67,18 +81,13 @@ if __name__ == '__main__':
if copied is None: if copied is None:
copied = 1 copied = 1
if mfa_data[0] == 'steam': if mfa_data[0] == 'steam':
try: _mfa_key = steam_otp(b32decode(mfa_data[1]))
from steam.guard import generate_twofactor_code as steam_totp
_mfa_key = steam_totp(b32decode(mfa_data[1]))
except ModuleNotFoundError:
print('\n\u001b[38;5;9merror: steam module not found\n\ninstall with "pip install -U \'steam[client]\'"\u001b[0m\n')
s_exit(6)
else: else:
_mfa_key = totp(mfa_data[1], mfa_data[2], mfa_data[3], mfa_data[4]) _mfa_key = totp(mfa_data[1], mfa_data[2], mfa_data[3], mfa_data[4])
if 'WSL_DISTRO_NAME' in environ: # WSL clipboard detection if 'WSL_DISTRO_NAME' in environ: # WSL clipboard detection
run(('powershell.exe', '-c', 'Set-Clipboard', _mfa_key)) run(('powershell.exe', '-c', "Set-Clipboard '" + _mfa_key + "'"))
elif 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection elif 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
run(('wl-copy', _mfa_key)) run('wl-copy', stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
elif uname()[0] == 'Haiku': # Haiku clipboard detection elif uname()[0] == 'Haiku': # Haiku clipboard detection
run(('clipboard', '-c', _mfa_key)) run(('clipboard', '-c', _mfa_key))
elif uname()[0] == 'Darwin': # MacOS clipboard detection elif uname()[0] == 'Darwin': # MacOS clipboard detection
@@ -89,5 +98,4 @@ if __name__ == '__main__':
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout) run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
sleep(1) sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
print('\n')
s_exit(0) s_exit(0)
+5
View File
@@ -0,0 +1,5 @@
[sshyp-mfa]
desc = read mfa data from sshyp entries to generate and copy totp keys to the clipboard (optional Steam support)
usage = sshyp /<entry name> copy -m
exe = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.1.2/extensions/sshyp-mfa/sshyp-mfa.py
ini = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.1.2/extensions/sshyp-mfa/sshyp-mfa.ini
+5
View File
@@ -0,0 +1,5 @@
[sshyp-mfa]
desc = reads mfa/2fa secrets from sshyp entries to generate and copy totp/Steam otp keys to the clipboard
usage = sshyp /<entry name> copy -m<br><br>to add mfa data to a sshyp entry, insert it into the SECOND notes line for a given entry using the following format:<br><br> otpauth://<OTP METHOD>/<ACCOUNT NAME, NOT USED>?secret=<SECRET>&issuer=<ISSUER, NOT USED>&algorithm=<ALGORITHM>&digits=<DIGITS>&period=<REFRESH PERIOD><br><br>what to put in each of the above spaces:<br><br> <OTP METHOD> is almost always 'totp', but in the case of Steam, it needs to be set to 'steam'.<br> <ACCOUNT NAME, NOT USED> is a part of the Authenticator backup format, but it is not used in sshyp-mfa. Set to anything.<br> <SECRET> refers to the secret used to generate your MFA key. This is usually directly provided by the issuer, but it is sometimes hidden and more easily retrieved by copying it from a QR-compatible MFA app (such as Aegis).<br> <ISSUER, NOT USED> is a part of the Authenticator backup format, but it is not used in sshyp-mfa. Set to anything.<br> <ALGORITHM> refers to the algorithm used to generate your MFA key based on your secret. This is almost always 'sha1'.<br> <DIGITS> refers to the intended length of your MFA key. This is almost always '6', but in the case of Steam, it needs to be set to '5'.<br> <REFRESH PERIOD> refers to the interval at which a new MFA key needs to be generated. This is almost always '30', for 30 seconds.<br><br>examples:<br><br> GitHub (standard 6-digit totp):<br> otpauth://totp/MyNameIsBob?secret=YUGBSG65SG9SDBSDF56SBFVSC86SBVD6&issuer=GitHub&algorithm=sha1&digits=6&period=30<br><br> Steam (5-character totp):<br> otpauth://steam/SteamUser?secret=VGVG34GH2GJHVCK7HGVS7&issuer=Steam&algorithm=sha1&digits=5&period=30<br><br>for more information, visit:<br><br> https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa
exe = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.1.3/extensions/sshyp-mfa/sshyp-mfa.py
ini = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.1.3/extensions/sshyp-mfa/sshyp-mfa.ini