65 Commits
Author SHA1 Message Date
RandyTheSilly 11f1fa2a0b Re-base conversion scripts on libmutton v0.5.0 2026-02-17 21:16:35 -05:00
RandyTheSilly 2e3eb2fea3 export-to-libmutton: Update usage info for libmutton v0.4.X 2025-06-19 18:26:15 -04:00
RandyTheSilly 4b0f45c5e2 upgradeRCW: Bump direct dependencies 2025-06-19 18:19:17 -04:00
RandyTheSilly 619ea52f7a gpgToRCW: Bump all direct dependencies 2025-06-19 18:16:43 -04:00
RandyTheSilly d63d77e69f upgradeRCW: Indicate old/new passphrase entry fields 2025-05-21 15:51:11 +00:00
RandyTheSilly a908beaa92 Add conversion program for converting between versions of RCW 2025-05-20 17:32:58 +00:00
RandyTheSilly e40dd66d38 gpgToRCW: go mod tidy 2025-05-20 16:27:04 +00:00
RandyTheSilly a22b803288 gpgToRCW: Bump libmutton to latest development version 2025-05-20 16:23:15 +00:00
RandyTheSilly 6e5a3b8f5b gpgToRCW: Trim .gpg suffixes from source files (useful for Pass migrations) 2025-05-20 14:14:40 +00:00
RandyTheSilly 626ddcce30 gpgToRCW: Bump to RCW v0.2.0 format 2025-05-20 14:10:14 +00:00
RandyTheSilly 0985a168db gpgToRCW: Bump dependencies to align with latest RCW/libmutton versions 2025-05-11 15:54:16 -04:00
RandyTheSilly f62122d79f Remove commit.sh 2025-05-11 00:11:53 -04:00
RandyTheSilly ab691c16d8 Add initial gpgToRCW conversion program 2025-05-11 00:11:25 -04:00
RandyTheSilly ada8a20a8f Add MUTN banner image 2024-07-30 15:50:49 -04:00
RandyTheSilly efcfe248ba Update README.md 2024-07-19 03:41:35 -04:00
RandyTheSilly e075f70ccf Add promotional MUTN VHS image and the means to reproduce it 2024-07-19 03:01:01 -04:00
RandyTheSilly 029032c74c [export-to-libmutton] Fix some notes being lost during MFA extraction, fix TOTP secrets being appended to incorrect entries 2024-05-21 16:18:13 -04:00
RandyTheSilly b722321a6e [export-to-libmutton] Support entries without .gpg extension 2024-05-21 14:26:25 -04:00
RandyTheSilly 4fa5b11fec [export-to-libmutton] Extract TOTP keys and move to dedicated field specified by the libmutton spec 2024-05-21 14:21:02 -04:00
RandyTheSilly 044bf2b205 [export-to-libmutton] Improve visuals of process termination warning 2024-05-21 13:20:52 -04:00
RandyTheSilly 9b44f438d4 [export-to-libmutton] Fix various issues 2024-05-21 13:17:15 -04:00
RandyTheSilly df9d32c715 Implement initial test for export-to-libmutton extension 2024-05-21 11:37:56 -04:00
RandyTheSilly e84e9569f3 Delete accidentally committed package 2023-12-08 19:08:43 -05:00
RandyTheSilly cfe9dd1eef Update pointer for release v1.5.1.3 2023-12-08 18:45:31 -05:00
RandyTheSilly cb1aca70bc sshyp-mfa - Bump Haiku/Termux package version to v1.5.1.3 2023-12-08 18:29:04 -05:00
RandyTheSilly 80425bec2e Add pointer for future sshyp release v1.5.2 2023-12-08 18:19:57 -05:00
RandyTheSilly d72c4a5c12 sshyp-mfa - Fix packaging for Haiku/Termux 2023-10-26 17:01:41 -04:00
RandyTheSilly d339e4c734 sshyp-mfa - Bump package version to v1.5.1.2 to build Haiku/Termux packages 2023-10-25 22:56:55 -04:00
RandyTheSilly 240939178e Read quick_unlock_enabled as a boolean value 2023-10-25 14:27:47 -04:00
RandyTheSilly b98122f4d8 Update pointer for release v1.5.1.2 2023-10-25 14:14:19 -04:00
RandyTheSilly 7be5509fa7 sshyp-mfa - Update to newer clipboard methods for WSL and Wayland 2023-10-25 10:53:14 -04:00
RandyTheSilly 8413c305c2 sshyp-mfa - Support Steam without external dependencies 2023-10-25 10:32:47 -04:00
RandyTheSilly 25a5731936 Add pointer for future sshyp release v1.5.1(.1) 2023-10-19 18:39:13 -04:00
RandyTheSilly 286645dd31 sshyp-mfa - Add support for quick-unlock with custom ssh keys 2023-10-19 18:36:12 -04:00
RandyTheSilly ec633c5104 sshyp-mfa - Removed extra blank line printed after a keyboard interrupt 2023-09-24 15:47:02 -04:00
RandyTheSilly 9540e9874f Fixed typo in Termux package name 2023-07-12 20:25:02 -04:00
RandyTheSilly 0020ac313b Fixed typo in packaging version number 2023-07-12 20:19:53 -04:00
RandyTheSilly ee8d875095 Fixed typo in Haiku packaging 2023-07-12 20:05:27 -04:00
RandyTheSilly 5d1eeb1f75 Update pointer for release v1.5.0.2 2023-07-12 19:41:48 -04:00
Randall Winkhart 769f522b2a Update README.md 2023-07-12 19:38:21 -04:00
RandyTheSilly 018912cfc5 sshyp-mfa - Changed Termux package to depend on sshyp-client 2023-07-10 12:25:43 -04:00
RandyTheSilly d53fc304d5 sshyp-mfa - Added temporary packaging script for Haiku and Termux 2023-07-10 11:17:41 -04:00
RandyTheSilly adcb13fd89 sshyp-mfa - Fixed inability to work in offline mode 2023-07-10 10:49:16 -04:00
RandyTheSilly 2421bc2ff3 Update pointer for v1.5.0 2023-07-07 08:38:23 -04:00
Randall Winkhart 0a4efc2002 Merge pull request #4 from rwinkhart/ext-manager
Merge ext-manager into main
2023-07-06 13:27:55 -04:00
RandyTheSilly 90884b4cd7 Added error message when generating Steam TOTP keys without dependency 2023-07-06 13:21:40 -04:00
RandyTheSilly bc09ec5e30 sshyp-mfa - Removed file extension expectation 2023-07-06 10:10:30 -04:00
RandyTheSilly e9a1728c6f Update variable names and file extensions to be more clear 2023-07-06 08:57:47 -04:00
RandyTheSilly 08bc5dec87 sshyp-mfa - Call extension directly, rather than through a symlink 2023-06-27 15:17:54 -04:00
Randall Winkhart 23be7f8c30 Delete password-pasture directory 2023-06-27 14:52:44 -04:00
Randall Winkhart 0bbcda37f2 Delete sshyp-mfa directory 2023-06-27 14:52:35 -04:00
Randall Winkhart 19ff50a5db Delete version 2023-06-27 14:51:21 -04:00
RandyTheSilly ac035db597 Re-organized repo for upcoming v1.5.0 extension management changes 2023-06-27 14:49:03 -04:00
RandyTheSilly fb267085eb sshyp-mfa - Compatibility with new tmpfs-less operation 2023-06-18 00:49:33 -04:00
RandyTheSilly 90c3de83b5 sshyp-mfa - Compatibility with new sshyp.decrypt() 2023-06-15 21:01:36 -04:00
RandyTheSilly a635207cee sshyp-mfa - Fixed clipboard typos 2023-06-15 19:25:59 -04:00
RandyTheSilly b9b06a4800 sshyp-mfa - Removed unused imports 2023-06-15 19:05:05 -04:00
RandyTheSilly 4e76b641a6 Updated compatible versions on refresh script 2023-06-13 10:13:52 -04:00
RandyTheSilly ed40b01e85 Support for new sshyp configuration file 2023-05-16 11:06:29 -04:00
RandyTheSilly c8e54cef43 sshyp-mfa - Use printf for X11 clipboard 2023-05-15 13:43:46 -04:00
RandyTheSilly 8fe7675ca3 sshyp-mfa - Added MacOS clipboard support 2023-05-15 13:39:56 -04:00
RandyTheSilly 45fd0631ca sshyp-mfa - Replaced subprocess lists with tuples 2023-05-15 13:33:21 -04:00
RandyTheSilly d4ed9b0da1 sshyp-mfa - Partial packaging parity with sshyp v1.4.2 2023-05-08 09:03:52 -04:00
RandyTheSilly 0953f60d0b sshyp-mfa - Added WSL clipboard support 2023-05-05 11:40:29 -04:00
RandyTheSilly 580d511a50 Updated packaging script to reflect changes in the sshyp packaging script 2023-03-29 16:37:52 -04:00
55 changed files with 733 additions and 432 deletions
+4
View File
@@ -0,0 +1,4 @@
/extra/conversion-scripts/gpgToRCW/1output
/extra/conversion-scripts/gpgToRCW/gpgToRCW
/extra/conversion-scripts/upgradeRCW/1output
/extra/conversion-scripts/upgradeRCW/upgradeRCW
+14 -13
View File
@@ -1,22 +1,23 @@
# sshyp-labs
Experimental extensions for the sshyp password manager.
Extensions for the [sshyp password manager](https://github.com/rwinkhart/sshyp).
sshyp-labs is currently the home of sshyp-mfa and password-pasture (sshyp-gui).
More recently, this repository has become a dumping ground for assets related to sshyp and its successor, MUTN.
# Installation
For most sshyp-supported platforms, sshyp extensions should be installed from the `sshyp tweak` menu's "extension management" option.
For **Haiku and Termux _ONLY_**, use the packages from the [releases page](https://github.com/rwinkhart/sshyp-labs/releases).
# Available Extensions
sshyp-mfa - [installation and usage instructions](https://github.com/rwinkhart/sshyp-labs/wiki/sshyp-mfa)
[export-to-libmutton](https://github.com/rwinkhart/sshyp-labs/wiki/export%E2%80%90to%E2%80%90libmutton): export sshyp entries for usage in libmutton-based password managers
sshyp-mfa is a unique approach to generating multi-factor authentication keys. Upon running `sshyp </entry name> copy -m`,
an MFA key will be generated and copied to your clipboard. sshyp-mfa will continue to run in the background and copy a
new key to your clipboard every time the actively copied one expires. Never worry about a TOTP timer expiring again!
At any time, sshyp-mfa can be closed with ctrl+c to stop this process.
[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 - [installation and usage instructions](https://github.com/rwinkhart/sshyp-labs/wiki/password-pasture)
password-pasture is an incredibly experimental GTK4 GUI interface for sshyp. It is currently not in a usable state
and has not been updated since sshyp v1.1.X. Development is set to resume after sshyp is more feature-complete.
[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
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).
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.
@@ -0,0 +1,3 @@
[config]
input = export
output = /usr/lib/sshyp/export-to-libmutton
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
from os import path, remove, system, walk
from pathlib import Path
from shutil import rmtree
from sshyp import decrypt
if __name__ == "__main__":
_pasture = path.expanduser('~/.local/share/sshyp')
_gpg_id = input('\nWhat is the ID of the GPG key you would like to use for re-encryption?\n\nID: ')
# remove previous export if it exists
if Path(f"{_pasture}.export").exists():
rmtree(f"{_pasture}.export")
# prompt for unlock and display do not close warning
decrypt(None)
print('\nentry conversion may take some time (especially on slower devices) - do not terminate this process!\n')
# iterate over entires, exporting them in the libmutton format
if path.isdir(_pasture):
for _dirPath, _dirNames, _filenames in walk(_pasture):
for _filename in sorted(_filenames):
# ensure parent directory within export tree exists
Path(_dirPath.replace(_pasture, _pasture + '.export')).mkdir(0o700, parents=True, exist_ok=True)
# remove the .gpg file extension, if it is present
# prevents saving with the extension, as libmutton does not require it
_og_extension = ''
if _filename.endswith('.gpg'):
_og_extension = '.gpg'
_filename = _filename[:-4]
# decrypt to export directory, append .gpg to decrypted file to differentiate from new file
system(f"gpg -d '{_dirPath}/{_filename}{_og_extension}' > '{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}.gpg'")
_old_contents, _new_contents = open(f"{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}.gpg", 'r').readlines(), ''
# extract sshyp-mfa data for new reserved line
_secret = '\n'
if len(_old_contents) >= 5:
if _old_contents[4].startswith('otpauth://'):
# check if sshyp-mfa data is standard TOTP or Steam
if _old_contents[4][10:].startswith('steam'):
print("\n\u001b[38;5;3mlibmutton uses a different form of secret key for Steam, and as such, your Steam TOTP secret must be migrated manually\u001b[0m\n")
else:
# extract TOTP secret
_secret = _old_contents[4].split('?secret=')[1].split('&issuer=')[0]+'\n'
# temporarily expand _old_contents to avoid index errors (will be trimmed later)
while len(_old_contents) < 6:
_old_contents.append('\n')
# extract any possible notes from _old_contents (as a string)
if _secret == '\n':
_old_notes = ''.join(_old_contents[3:])
else:
# skip second notes line if sshyp-mfa data was found
# skip first notes line if blank line
if _old_contents[3] in ('\n', ' '):
_old_contents[3] = ''
_old_notes = _old_contents[3] + ''.join(_old_contents[5:])
# set _new_contents
_new_contents = _old_contents[0] + _old_contents[1] + _secret + _old_contents[2] + _old_notes
# remove any trailing whitespace
for _num in reversed(range(len(_new_contents))):
if _new_contents[_num] in ('\n', '', ' '):
_new_contents = _new_contents[:-1]
else:
break
# write and encrypt the output file
open(f"{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}.gpg", 'w').writelines(_new_contents)
system(f"gpg -er {str(_gpg_id)} -o '{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}' '{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}.gpg'")
remove(f"{_dirPath.replace(_pasture, _pasture + '.export')}/{_filename}.gpg")
else:
print(f"\n\u001b[38;5;9merror: no entry folder ({_pasture}) found\u001b[0m\n")
s_exit(2)
+80
View File
@@ -0,0 +1,80 @@
#!/bin/sh
version='1.5.1.3'
if [ -z "$2" ]; then
revision=1
else
revision="$2"
fi
_create_hpkg() {
printf '\npackaging for Haiku...\n'
mkdir -p output/haikutemp/lib/sshyp/extensions
printf "name sshyp_mfa
version "$version"-"$revision"
architecture any
summary \"An MFA (TOTP/Steam) key generator for the sshyp password manager\"
description \"sshyp-mfa is an extension for the sshyp password manager that reads MFA data from sshyp entries and generates generic TOTP and Steam keys.\"
packager \"Randall Winkhart <idgr at tutanota dot com>\"
vendor \"Randall Winkhart\"
licenses {
\"GNU GPL v3\"
}
copyrights {
\"2021-2023 Randall Winkhart\"
}
provides {
sshyp_mfa = "$version"
}
requires {
sshyp_client
}
urls {
\"https://github.com/rwinkhart/sshyp-labs\"
}
" > output/haikutemp/.PackageInfo
cp ./sshyp-mfa.py output/haikutemp/lib/sshyp/sshyp-mfa
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.11/' output/haikutemp/lib/sshyp/sshyp-mfa
cd output/haikutemp
package create -b HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg
package add HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg lib
cd ../..
mv output/haikutemp/HAIKU-sshyp_mfa-"$version"-"$revision"_all.hpkg output/
rm -rf output/haikutemp
printf "\nHaiku packaging complete\n\n"
} &&
_create_termux() {
printf '\npackaging for Termux...\n'
mkdir -p output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/DEBIAN \
output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/extensions
printf "Package: sshyp-mfa
Version: $version
Section: utils
Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: An MFA (TOTP/Steam) key generator for the sshyp password manager
Depends: sshyp-client
Priority: optional
Installed-Size: 100
" > 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
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/
mv output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux.deb output/TERMUX-sshyp-mfa_"$version"-"$revision"_all.deb
rm -rf output/termuxtemp
printf '\nTermux packaging complete\n\n'
} &&
case "$1" in
haiku)
_create_hpkg
;;
termux)
_create_termux
;;
*)
printf '\nusage: package.sh [target] <revision>\n\ntargets: haiku termux\n\n'
;;
esac
+3
View File
@@ -0,0 +1,3 @@
[config]
input = copy -m
output = /usr/lib/sshyp/sshyp-mfa
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
from base64 import b32decode
from configparser import ConfigParser
from hmac import new as hmac_new
from os import environ, listdir, uname
from os.path import expanduser, isdir, isfile
from sshyp import decrypt, whitelist_verify
from struct import pack, unpack
from subprocess import PIPE, Popen, run
from sys import argv, exit as s_exit
from time import sleep, strftime, time
home = expanduser("~")
def totp(_secret, _algo, _digits, _period): # uses provided information to generate a standard totp key
_secret = b32decode(_secret.upper() + '=' * ((8 - len(_secret)) % 8))
_counter = pack('>Q', int(time() / _period))
_hmac = hmac_new(_secret, _counter, _algo).digest()
_offset = _hmac[-1] & 0x0f
_binary = unpack('>L', _hmac[_offset:_offset + 4])[0] & 0x7fffffff
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
if not isfile(f"{directory}{arguments[0]}.gpg"):
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not exist\u001b[0m\n")
s_exit(1)
if quick_unlock_enabled:
_mfa_data = decrypt(directory + arguments[0],
_quick_pass=whitelist_verify(sshyp_data.get('SSHYNC', 'port'),
sshyp_data.get('SSHYNC', 'user'),
sshyp_data.get('SSHYNC', 'ip'),
listdir(f"{home}/.config/sshyp/devices")[0],
sshyp_data.get('SSHYNC', 'identity_file')))
else:
_mfa_data = decrypt(directory + arguments[0])
try:
_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])
return _type, _secret, _algo, _digits, _period
except IndexError:
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not contain valid mfa data\u001b[0m\n")
s_exit(1)
if __name__ == '__main__':
# argument fetcher
arguments = argv[1:]
if len(arguments) < 1 or not arguments[0].startswith('/'):
print("\nsshyp-mfa extension usage: sshyp </entry name> copy -m\n\nrun 'man sshyp-mfa' for more information\n")
s_exit(1)
# user data fetcher
sshyp_data = ConfigParser()
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
directory = f"{home}/.local/share/sshyp/"
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
try:
mfa_data, copied = mfa_read_shortcut(), None
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:
if str(int(strftime('%S'))/mfa_data[4]).endswith('.0') or copied is None:
if copied is None:
copied = 1
if mfa_data[0] == 'steam':
_mfa_key = steam_otp(b32decode(mfa_data[1]))
else:
_mfa_key = totp(mfa_data[1], mfa_data[2], mfa_data[3], mfa_data[4])
if 'WSL_DISTRO_NAME' in environ: # WSL clipboard detection
run(('powershell.exe', '-c', "Set-Clipboard '" + _mfa_key + "'"))
elif 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
run('wl-copy', stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
elif uname()[0] == 'Haiku': # Haiku clipboard detection
run(('clipboard', '-c', _mfa_key))
elif uname()[0] == 'Darwin': # MacOS clipboard detection
run('pbcopy', stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
elif isdir("/data/data/com.termux"): # Termux (Android) clipboard detection
run(('termux-clipboard-set', _mfa_key))
else: # X11 clipboard detection
run(('xclip', '-sel', 'c'), stdin=Popen(('printf', _mfa_key), stdout=PIPE).stdout)
sleep(1)
except KeyboardInterrupt:
s_exit(0)
Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

@@ -1 +0,0 @@
89fbbcf15e7fb786db4e2551dc3234615c57f77e
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# This script generates portable release binaries for the following platforms:
# - Linux (x86_64_v1)
# - Linux (aarch64)
# - Windows (x86_64_v1)
# - Windows (aarch64)
mkdir -p ./1output
GOOS=linux CGO_ENABLED=0 GOAMD64=v1 go build -o ./1output/gpgToRCW-linux-x86_64_v1 -ldflags="-s -w" -trimpath .
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o ./1output/gpgToRCW-linux-aarch64 -ldflags="-s -w" -trimpath .
GOOS=windows CGO_ENABLED=0 GOAMD64=v1 go build -o ./1output/gpgToRCW-windows-x86_64_v1.exe -ldflags="-s -w" -trimpath .
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -o ./1output/gpgToRCW-windows-aarch64.exe -ldflags="-s -w" -trimpath .
+19
View File
@@ -0,0 +1,19 @@
module gpgToRCW
go 1.26.0
require (
github.com/rwinkhart/go-boilerplate v0.3.1
github.com/rwinkhart/libmutton v0.5.0
github.com/rwinkhart/rcw v0.3.0
)
require (
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
)
replace golang.org/x/sys => github.com/rwinkhart/sys v0.41.0
replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio v0.1.1
+12
View File
@@ -0,0 +1,12 @@
github.com/rwinkhart/go-boilerplate v0.3.1 h1:vkVRuptO2s1yPzzwpvtiiB0c/hPB6yr0p1mzZ2Myb9E=
github.com/rwinkhart/go-boilerplate v0.3.1/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes=
github.com/rwinkhart/libmutton v0.5.0 h1:Du/o5Hyb26uE6lubqBISN/ZF9JZ5oObHVveQjAqrP0E=
github.com/rwinkhart/libmutton v0.5.0/go.mod h1:peh8A1f3gnaF97m8J06U28JloKv04Kw8WtFCH+Pcq6s=
github.com/rwinkhart/rcw v0.3.0 h1:OdgA++IGcDrZqN6FzyYnzbz04fL8Q1MHvlgPjhtgJM4=
github.com/rwinkhart/rcw v0.3.0/go.mod h1:UUY7kSku6kV2X1RWPUcK0Re+rQ6RvngWI8DVZJ11cqk=
github.com/rwinkhart/sys v0.41.0 h1:pHB6HphVC132UXYZ6yeOk62abqgl+pyl5ZZnsk4iUKg=
github.com/rwinkhart/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
+19
View File
@@ -0,0 +1,19 @@
package main
import (
"os/exec"
"strings"
"github.com/rwinkhart/go-boilerplate/other"
"github.com/rwinkhart/libmutton/global"
)
// decryptGPG decrypts a GPG-encrypted file and returns the contents as a slice of (trimmed) strings.
func decryptGPG(targetLocation string) []string {
cmd := exec.Command("gpg", "--pinentry-mode", "loopback", "-q", "-d", targetLocation)
output, err := cmd.Output()
if err != nil {
other.PrintError("Failed to decrypt \""+targetLocation+"\" - Ensure it is a valid GPG-encrypted file and that you entered your passphrase correctly", global.ErrorDecryption)
}
return strings.Split(string(output), "\n")
}
+62
View File
@@ -0,0 +1,62 @@
package main
import (
"bytes"
"fmt"
"os"
"strings"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/go-boilerplate/other"
"github.com/rwinkhart/libmutton/global"
"github.com/rwinkhart/libmutton/synccommon"
"github.com/rwinkhart/rcw/wrappers"
)
func main() {
// get info from user
var rcwPassphrase []byte
for {
rcwPassphrase = front.InputSecret("RCW passphrase:")
if !bytes.Equal(rcwPassphrase, front.InputSecret("Confirm RCW passphrase:")) {
fmt.Println(back.AnsiError + "Passphrases do not match" + back.AnsiReset)
continue
}
break
}
// convert the entries
fmt.Print("\nRe-encrypting entries. Please wait; do not force close this process.\n\nGPG may prompt you for a passphrase for decryption.\n\n")
var outputDir = global.EntryRoot + "-new"
entries, folders, err := synccommon.WalkEntryDir()
if err != nil {
other.PrintError("Failed to walk entry directory: "+err.Error(), back.ErrorRead)
}
for _, folder := range folders {
err := os.MkdirAll(outputDir+strings.ReplaceAll(folder, "/", global.PathSeparator), 0700)
if err != nil {
other.PrintError("Failed to create directory: "+err.Error(), back.ErrorWrite)
}
}
var decLines []string
for _, entry := range entries {
entry = strings.TrimSuffix(entry, ".gpg")
decLines = decryptGPG(global.GetRealPath(entry))
encBytes := wrappers.Encrypt([]byte(strings.Join(decLines, "\n")), rcwPassphrase, true, false)
err := os.WriteFile(outputDir+strings.ReplaceAll(entry, "/", global.PathSeparator), encBytes, 0600)
if err != nil {
other.PrintError("Failed to write to file: "+err.Error(), back.ErrorWrite)
}
}
// swap the new directory with the old one
err = os.Rename(global.EntryRoot, global.EntryRoot+"-old")
if err != nil {
other.PrintError("Failed to rename old directory: "+err.Error(), back.ErrorWrite)
}
err = os.Rename(outputDir, global.EntryRoot)
if err != nil {
other.PrintError("Failed to rename new directory: "+err.Error(), back.ErrorWrite)
}
}
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
# This script generates portable release binaries for the following platforms:
# - Linux (x86_64_v1)
# - Linux (aarch64)
# - Windows (x86_64_v1)
# - Windows (aarch64)
version=0.1.X-0.2.X
mkdir -p ./1output
GOOS=linux CGO_ENABLED=0 GOAMD64=v1 go build -o "./1output/upgradeRCW-$version-linux-x86_64_v1" -ldflags="-s -w" -trimpath .
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o "./1output/upgradeRCW-$version-linux-aarch64" -ldflags="-s -w" -trimpath .
GOOS=windows CGO_ENABLED=0 GOAMD64=v1 go build -o "./1output/upgradeRCW-$version-windows-x86_64_v1.exe" -ldflags="-s -w" -trimpath .
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -o "./1output/upgradeRCW-$version-windows-aarch64.exe" -ldflags="-s -w" -trimpath .
@@ -0,0 +1,22 @@
module upgradeRCW
go 1.26.0
require (
github.com/rwinkhart/convertroman v0.2.0 // DECOY TO TRICK GO INTO USING TWO VERSIONS OF RCW
github.com/rwinkhart/go-boilerplate v0.3.1
github.com/rwinkhart/libmutton v0.5.0
github.com/rwinkhart/rcw v0.3.0
)
require (
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
)
replace github.com/rwinkhart/convertroman => github.com/rwinkhart/rcw v0.1.2
replace golang.org/x/sys => github.com/rwinkhart/sys v0.41.0
replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio v0.1.1
@@ -0,0 +1,14 @@
github.com/rwinkhart/go-boilerplate v0.3.1 h1:vkVRuptO2s1yPzzwpvtiiB0c/hPB6yr0p1mzZ2Myb9E=
github.com/rwinkhart/go-boilerplate v0.3.1/go.mod h1:ES13A2r9fnCVfyezwMBgY/RgA4pOIudOUXz3Jk/ikes=
github.com/rwinkhart/libmutton v0.5.0 h1:Du/o5Hyb26uE6lubqBISN/ZF9JZ5oObHVveQjAqrP0E=
github.com/rwinkhart/libmutton v0.5.0/go.mod h1:peh8A1f3gnaF97m8J06U28JloKv04Kw8WtFCH+Pcq6s=
github.com/rwinkhart/rcw v0.1.2 h1:JFNRx5Uc0VKZMbur05VbZBhfUtOp2KBjZ6UDeaCRYFI=
github.com/rwinkhart/rcw v0.1.2/go.mod h1:LQ2/YJFQlkW+JkgrLpZ51NQP/unIEzPTWsLOpOuKdKU=
github.com/rwinkhart/rcw v0.3.0 h1:OdgA++IGcDrZqN6FzyYnzbz04fL8Q1MHvlgPjhtgJM4=
github.com/rwinkhart/rcw v0.3.0/go.mod h1:UUY7kSku6kV2X1RWPUcK0Re+rQ6RvngWI8DVZJ11cqk=
github.com/rwinkhart/sys v0.41.0 h1:pHB6HphVC132UXYZ6yeOk62abqgl+pyl5ZZnsk4iUKg=
github.com/rwinkhart/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
@@ -0,0 +1,77 @@
package main
import (
"bytes"
"fmt"
"os"
"strings"
oldwrap "github.com/rwinkhart/convertroman/wrappers"
newwrap "github.com/rwinkhart/rcw/wrappers"
"github.com/rwinkhart/go-boilerplate/back"
"github.com/rwinkhart/go-boilerplate/front"
"github.com/rwinkhart/go-boilerplate/other"
"github.com/rwinkhart/libmutton/global"
"github.com/rwinkhart/libmutton/synccommon"
)
func main() {
// get info from user
var oldRCWPassphrase, newRCWPassphrase []byte
for {
oldRCWPassphrase = front.InputSecret("Old RCW passphrase:")
if !bytes.Equal(oldRCWPassphrase, front.InputSecret("Confirm old RCW passphrase:")) {
fmt.Println(back.AnsiError + "Passphrases do not match" + back.AnsiReset)
continue
}
break
}
for {
newRCWPassphrase = front.InputSecret("New RCW passphrase:")
if !bytes.Equal(newRCWPassphrase, front.InputSecret("Confirm new RCW passphrase:")) {
fmt.Println(back.AnsiError + "Passphrases do not match" + back.AnsiReset)
continue
}
break
}
// convert the entries
fmt.Print("\nRe-encrypting entries. Please wait; do not force close this process.\n\n")
var outputDir = global.EntryRoot + "-new"
entries, folders, err := synccommon.WalkEntryDir()
if err != nil {
other.PrintError("Failed to walk entry directory: "+err.Error(), back.ErrorRead)
}
for _, folder := range folders {
err := os.MkdirAll(outputDir+strings.ReplaceAll(folder, "/", global.PathSeparator), 0700)
if err != nil {
other.PrintError("Failed to create directory: "+err.Error(), back.ErrorWrite)
}
}
for _, entry := range entries {
oldEncBytes, err := os.ReadFile(global.GetRealPath(entry))
if err != nil {
other.PrintError("Failed to read file: "+err.Error(), back.ErrorRead)
}
decBytes, err := oldwrap.Decrypt(oldEncBytes, oldRCWPassphrase)
if err != nil {
other.PrintError("Failed to decrypt file: "+err.Error(), back.ErrorWrite)
}
newEncBytes := newwrap.Encrypt(decBytes, newRCWPassphrase, true, false)
err = os.WriteFile(outputDir+strings.ReplaceAll(entry, "/", global.PathSeparator), newEncBytes, 0600)
if err != nil {
other.PrintError("Failed to write to file: "+err.Error(), back.ErrorWrite)
}
}
// swap the new directory with the old one
err = os.Rename(global.EntryRoot, global.EntryRoot+"-old")
if err != nil {
other.PrintError("Failed to rename old directory: "+err.Error(), back.ErrorWrite)
}
err = os.Rename(outputDir, global.EntryRoot)
if err != nil {
other.PrintError("Failed to rename new directory: "+err.Error(), back.ErrorWrite)
}
}
+130
View File
@@ -0,0 +1,130 @@
Require mutn
Require gpg
Set Theme { "name": "GruvboxDarkMUTNCustom", "black": "#282828", "red": "#cc241d", "green": "#98971a", "yellow": "#d79921", "blue": "#458588", "magenta": "#b16286", "cyan": "#689d6a", "white": "#d5c4a1", "brightBlack": "#928374", "brightRed": "#fb4934", "brightGreen": "#b8bb26", "brightYellow": "#fabd2f", "brightBlue": "#83a598", "brightMagenta": "#d3869b", "brightCyan": "#8ec07c", "brightWhite": "#ebdbb2", "background": "#272727", "foreground": "#d5c4a1", "selection": "#665c54", "cursor": "#ebdbb2" }
Set Padding 5
Set Framerate 30
Set Height 675
Set Width 1290
Set Shell zsh
Output mutn-demo.gif
Sleep 1s
Type "# start of loop | viewing my entry list :)"
Sleep 800ms
Backspace@25ms 42
Type "mutn"
Sleep 250ms
Enter
Sleep 2.25s
Type "# syncing entries with the server :o"
Sleep 500ms
Backspace@25ms 36
Type "mutn sync"
Enter
Sleep 1.25s
Type "clear"
Sleep 1.5s
Enter
Type "# reading my todo list :^)"
Sleep 500ms
Backspace@25ms 26
Type "mutn /notes/personal/todo"
Enter
Sleep 1s
Type "example"
Enter
Sleep 2s
Type "clear"
Sleep 2.5s
Enter
Type "# changing my github password :p"
Sleep 500ms
Backspace@25ms 32
Type "mutn /development/github gen -u"
Enter
Sleep 500ms
Type "69"
Sleep 500ms
Enter
Sleep 500ms
Type "y"
Sleep 500ms
Enter
Sleep 2s
Enter
Type "# copying the new password :]"
Sleep 500ms
Backspace@25ms 29
Up
Sleep 500ms
Backspace 6
Sleep 500ms
Type "copy"
Enter
Sleep 1s
Enter
Type "# darn, I need my totp token :/"
Sleep 800ms
Backspace@25ms 31
Up
Sleep 500ms
Type " -t"
Enter
Sleep 1s
Enter
Type "as long as the process runs, the token will be refreshed | let's paste it: "
Sleep 250ms
Paste
Sleep 1s
Backspace@25ms 81
Ctrl+c
Sleep 1s
Type "clear"
Sleep 1.5s
Enter
Type "# editing my todo list :|"
Sleep 500ms
Backspace@25ms 25
Type "mutn /notes/personal/todo edit -n"
Sleep 500ms
Enter
Escape
Sleep 1s
Type "jj"
Sleep 500ms
Type "2dd"
Sleep 1s
Type ":wq"
Sleep 500ms
Enter
Sleep 2s
Type "clear"
Sleep 2.5s
Enter
Type "# checking up on my homework :'("
Sleep 500ms
Backspace@25ms 32
Type "mutn /notes/school/homework"
Enter
Sleep 2s
Type "clear"
Sleep 2.5s
Enter
Type "# giving my school notes a better name ;3"
Sleep 500ms
Backspace@25ms 41
Type "mutn /notes/school/world\ history/MUTN\ public\ debut edit -r"
Enter
Sleep 1s
Type "/notes/school/world history/the best day ever"
Enter
Sleep 1.5s
Type "mutn"
Sleep 250ms
Enter
Sleep 1s
Type "# end of loop! (:"
Sleep 3s
+17
View File
@@ -0,0 +1,17 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
lIYEZpmhGhYJKwYBBAHaRw8BAQdAtMOex5PpSNmm45m78oWwiyL7+HzpMXEfsJuo
sVbQVkn+BwMCxhrqdwXRs/T/zvz/OO16ugFb9USURoP2Q9qxob5xHu/pitT2BKJ0
AmqFgJ7d+XBhUYUgyghFs4ghnbJ+idc3M97gaUu3LTWVTN4z4WCDP7RFbGlibXV0
dG9uLTE3MjEzNDQyODIgKGdwZy1saWJtdXR0b24pIDxnaXRodWIuY29tL3J3aW5r
aGFydC9saWJtdXR0b24+iJMEExYKADsWIQRKkDJcKDRJ8GqC6UGj5HQatDKA/QUC
ZpmhGgIbAwULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRCj5HQatDKA/YPC
AP0a0uWYfPFt7ql6uqTF8ybH1CWlrcCdKH32Yt0TbzMFuQEAoILlIqeSznySva/v
V/vEoKjghrlOxYS1XooR2VmLXg6ciwRmmaEaEgorBgEEAZdVAQUBAQdAjCCCC4Vq
t6VILdm8aL8A1Lxld3QznrjastwbS5xGyw0DAQgH/gcDAiY9vEXhi4Uq/x3t0G8S
iIbkoNaAe9r8jI/ju2FaqdDSo/kPGm/BdsifQqdfq8OuWIGYTsCItSk3nkfc6lYm
zdIVFahsMVW9ATz8Jd8Gnp2IeAQYFgoAIBYhBEqQMlwoNEnwaoLpQaPkdBq0MoD9
BQJmmaEaAhsMAAoJEKPkdBq0MoD9ulcBAN1jS5Nby4s1wGCGBfMFj4AxTBSvoWB9
CUponolEwl5MAP9VS6scwf/sOkpohn3d9PBnCioGxR4+N4JNzrBQFE7ECQ==
=bGiv
-----END PGP PRIVATE KEY BLOCK-----
Binary file not shown.
@@ -0,0 +1,2 @@
„^ůRb dJ@ WwÔU=KŃ.ĆŃ‹ÝÖ> Ź":0bÉţÔ•"0'fŰv—šŔř=~ť#đÓźô•©ťáćvPšŘ
ŰE˘z Ö{6'1ˇţpÔŻ Đ4®ŘÄ4SçŇ›ÖZú`dÎ)ć$Šj|ÂŘŤg_|ŤAłěâă7ż=‘§Ě/8č'žÜ†ěÇﱫ$$ă¶ #»šTd;$]Ţ*?űŹÍż[ţTüĹ_ćW™á˛ĄŃ‰N:(Z«€łÝ°ܵNąZü$Z`ë_扸x&4ť+ѵŮh˙x|üKF}źaŤ9NŤ+saĆkşKŕúÝéŇnw>EŚËë0“+
Binary file not shown.
@@ -0,0 +1,2 @@
„^ůRb dJ@Näë'”Éäňń¦GÝ|ŤÉźj˙ĄÜh@# Bő–<0ď5Á”˘GË}=ÖúG2
]ź? wµ˝ýÔs„ß¶D†qŦ?!hÄ„&ľm,nÔM Í:V%`Ľ©ß(Woł05z'§N gdNÔą´­_‹Ó2DX ĎFAÉ„ZUĐĆŐÖzÍ.>\Ä_yJJßz“Z
Binary file not shown.
@@ -0,0 +1,2 @@
„^ùRb dJ@Vž„»)fåäð:€ü¨Ýq7€¦C5#¿WI“ß½0Ì—"æl6í>êr9†I°ùæz*€ŒãMøUý̵V"dÔÚ[à“¹à «òÑÿqÔÀ0 $¾ó/ë^®þt½Ÿu]‹õ
&ZaBqH:l}sRb.ov?È!¢Ø¬C¢ü¥ÁƦdzŒÒ=Á§Û­á!v”Et²;PDÚxO•8ÝSï$%þíï|E^Ã/Á›Ü!Œ§¾¬ˆý6° ÙÌÑHÝä$J úCNÞ¹fEÈâí÷ŠV·U¼JÖtL¡{ã“6‡è²—V*s]À×QTi]®Ö´cJON¹[¨O:NÍÙÒ:Ü#B´:™A:¼úÌ.*}–R̽w©‘ 7H6×zí¸¼ÆÁýD7°Ú¶ñ¢‘u(9í‰Ð°ßaŠóì?
@@ -0,0 +1,2 @@
„^ùRb dJ@Ô†W¢Ï›7¹ù¾áíN¨ËGïÑÄë²Ûö"õ× 0 Þâ, Ö“ÝœoØr͘è˜!Ú rÿL³ÁITdEL*ˆK7BèÒ7ÿh4¢óÔW >Û:A+ä?Aý~qÓ+í»©# F¸Á ú!¦z`ánOò¦òG“XÕ‘G]Ú¦õ
@£ínEz˜%ØþóGRY’™AÖLÏ%y›åÕ-c
@@ -0,0 +1,2 @@
„^ùRb dJ@Ê‹Èaâ¥Þ!á¾4Ùäó†uÄä‘Ìß™ÎH¯ýp>t0Ų½—'ef¾IpäS•p¤!©þh¦ùN%­šÃ).Û¯}µdÀªÌ¾c:êo?WœeÔX £)«zÒKåf‰N(ü t#df¡Á™‡#±˜€ÙÆÜEð¤Ðˆè_5I~Nñ&%c|AÃ
¬Ã¾/tSUOo`Á¼>G‘«6&k¥É¢-ö
@@ -0,0 +1 @@
„^щRb dJ@WЎе кЇzЖ¬pУv•YЉЮџрсD®ЖЊдiоІ v0 dОВ]Њ)тт,jњЄЁвиЯ‹LVQ- ѓП:З¤ЯР¬"}‘Ф=Ѓрv'6Ър§Ф_ §AГокУҐЙѓp,шAvЂhї0‡„gW†JqDїЇ]и„fPдЂЮЯЦT-Щ¬"ґ«’с41ї6ъW1уz^MЯвоў[соФСЎb€OПаA „ 7жH5
Binary file not shown.
@@ -0,0 +1 @@
„^ùRb dJ@JpëÁÈ<õÔMßÑv§‰µ¦ßJ¿'ª˜Z0åÞ5äö0ƒ UsÞA–Ø0&îS:ojÄb¡R!²´ŸÏÀÙ`]Œ(áLØŽ÷TËäÔV Ó®ÌF¶ã ­ÚŒüt¦TOr‰Ô¦ìVàïu€«?çC49·áåö²Æ >…ˆ­1µ%GŠ|Ã=¹‘_x^*OÎRüœ²¶?FA®?ùRà
Binary file not shown.
@@ -0,0 +1 @@
„^שRb dJ@Bנֹמ!z,tgeֳ›¢dW—­}5JoJ ²Tָ0¼ 3L* ˜<תuw¼ #£ˆd,M:zָg›µ)˜w­»95®ל{װ“ ײtBד·#½V–Dמה(C*uֵZןQ° TC§,g—ֶם]ם—;“ £ֽ:ָd×זVְ£ur£נ´ר2ט”7!&oםם¶מboָ[& ₪R¸,־rgq<ֶ"V<הװ«j„ֲ²Mבע¦Mן{QO?h¿9װjz°P^
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+14
View File
@@ -0,0 +1,14 @@
# MUTN VHS Tape Creation
1. Ensure `mutn` is in your path
2. If `zsh` is not installed, either install it or change the shell in `cassette.tape` (for some reason the "Up" function only works in `zsh` for me)
3. Import example.asc (if not done already) with password `example`
4. Set up a client-server pair using the `libmutton` entries folder provided in this repo
5. Run the following "reset" command from within this folder (it's okay if it throws errors; this is expected if running before using `vhs`):
- `rm -rf ~/.local/share/libmutton && cp -r ./libmutton ~/.local/share/libmutton && mutn sync && mutn /notes/school/world\ history/the\ best\ day\ ever shear; rm ~/.local/share/libmutton/notes/school/ccnp\ networking/{chapter\ 4,chapter\ 5} && gpgconf --kill gpg-agent`
6. Run `vhs ./cassette.tape`
7. View mutn-demo.gif! Use the above "reset" command between each recording.
# Read for Production
Before posting for production, I convert the GIF to an animated WEBP (to save space) using libwebp's `gif2webp`:
`gif2webp -min_size -q 100 -m 6 ./mutn-demo.gif -o ./mutn-demo.webp`
+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.0.2/extensions/sshyp-mfa/sshyp-mfa.py
ini = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/v1.5.0.2/extensions/sshyp-mfa/sshyp-mfa.ini
+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.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
+11
View File
@@ -0,0 +1,11 @@
[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
[export-to-libmutton]
desc = exports sshyp entries to ~/.local/share/sshyp.export for use with libmutton v0.3.X
usage = sshyp export<br><br>this extension does not export to a format compatible with libmutton v0.4.X or later!!<br><br>for use with libmutton v0.4.X or later, run the output of this extension through the gpgToRCW binaries provided at https://github.com/rwinkhart/sshyp-labs/releases/tag/v2.0.0
exe = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/main/extensions/export-to-libmutton/export-to-libmutton.py
ini = https://raw.githubusercontent.com/rwinkhart/sshyp-labs/main/extensions/export-to-libmutton/export-to-libmutton.ini
-44
View File
@@ -1,44 +0,0 @@
.TH sshyp-mfa 1 "04 March 2023" "v1.4.0.1" "sshyp-mfa man page"
.SH NAME
sshyp-mfa \- An MFA (TOTP/Steam) key generator for the sshyp password manager.
.SH SYNOPSIS
Extension Usage: sshyp </entry name> copy -m
Direct Usage: sshyp-mfa </entry name>
.SH DESCRIPTION
sshyp-mfa is an extension for the sshyp password manager that reads MFA data from sshyp entries and generates generic TOTP and Steam keys.
.SH EXAMPLES
Generating and copying an MFA key for an existing entry saved as ~/.local/share/sshyp-mfa/development/github.gpg
sshyp /development/github copy -m
sshyp-mfa /development/github
.SH SETUP
sshyp-mfa requires a pre-existing functional sshyp setup.
MFA keys are generated off of MFA data placed in the second line of a sshyp entry's notes field. This data needs to be added manually, through sshyp, and must be in Authenticator backup format.
It might be easiest to import all of your MFA keys into Authenticator, export them into plain text, then add all of the exported MFA data into the second notes line in their respective sshyp entries.
.SH FORMAT
Format: otpauth://<OTP METHOD>/<ACCOUNT NAME, NOT USED>?secret=<SECRET>&issuer=<ISSUER, NOT USED>&algorithm=<ALGORITHM>&digits=<DIGITS>&period=<REFRESH PERIOD>
Help! What do I put in each of those spaces?
<OTP METHOD> is almost always 'totp', but in the case of Steam, it needs to be set to 'steam'.
<ACCOUNT NAME, NOT USED> is a part of the Authenticator backup format, but it is not used in sshyp-mfa. Set to anything.
<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).
<ISSUER, NOT USED> is a part of the Authenticator backup format, but it is not used in sshyp-mfa. Set to anything.
<ALGORITHM> refers to the algorithm used to generate your MFA key based on your secret. This is almost always 'sha1'.
<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'.
<REFRESH PERIOD> refers to the interval at which a new MFA key needs to be generated. This is almost always '30', for 30 seconds.
Example configuration (for most services, see above for differences regarding Steam):
otpauth://totp/MyNameIsBob?secret=YUGBSG65SG9SDBSDF56SBFVSC86SBVD6&issuer=Github&algorithm=sha1&digits=6&period=30
.SH AUTHOR
Randall Winkhart (https://github.com/rwinkhart)
-3
View File
@@ -1,3 +0,0 @@
[config]
input = copy -m
output = sshyp-mfa
-90
View File
@@ -1,90 +0,0 @@
#!/usr/bin/env python3
from base64 import b32decode
from os import environ, listdir, path, uname
from pathlib import Path
from sshync import get_profile
from sshyp import decrypt, shm_gen, whitelist_verify
from subprocess import PIPE, Popen, run
from sys import argv, exit as s_exit
from time import sleep, strftime, time
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))
_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(): # extracts MFA info from the user-specified sshyp entry
from shutil import rmtree
if not Path(f"{directory}{arguments[0]}.gpg").exists():
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not exist\u001b[0m\n")
s_exit(1)
_shm_folder, _shm_entry = shm_gen()
if quick_unlock_enabled == 'y':
decrypt(directory + arguments[0], _shm_folder, _shm_entry, whitelist_verify(port, username_ssh, ip, device_id))
else:
decrypt(directory + arguments[0], _shm_folder, _shm_entry, False)
try:
_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
except IndexError:
print(f"\n\u001b[38;5;9merror: entry ({arguments[0]}) does not contain valid mfa data\u001b[0m\n")
rmtree(f"{path.expanduser('~/.config/sshyp/tmp/')}{_shm_folder}")
s_exit(1)
if __name__ == '__main__':
# argument fetcher
arguments = argv[1:]
if len(arguments) < 1 or not arguments[0].startswith('/'):
print("\nsshyp-mfa extension usage: sshyp </entry name> copy -m\n\nrun 'man sshyp-mfa' for more information\n")
s_exit(1)
# user data fetcher
device_id = listdir(path.expanduser('~/.config/sshyp/devices'))[0]
quick_unlock_enabled = open(path.expanduser('~/.config/sshyp/sshyp-data')).readlines()[3].rstrip()
ssh_info = get_profile(path.expanduser('~/.config/sshyp/sshyp.sshync'))
username_ssh = str(ssh_info[0].rstrip())
ip = str(ssh_info[1].rstrip())
port = str(ssh_info[2].rstrip())
directory = str(ssh_info[3].rstrip())
# main process: runs functions to generate MFA key, then continuously copies up-to-date MFA key to clipboard
try:
mfa_data, copied = mfa_read_shortcut(), None
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:
if str(int(strftime('%S'))/mfa_data[4]).endswith('.0') or copied is None:
if copied is None:
copied = 1
if mfa_data[0] == 'steam':
from steam.guard import generate_twofactor_code as steam_totp
_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 'WAYLAND_DISPLAY' in environ: # Wayland clipboard detection
run(['wl-copy', _mfa_key])
elif uname()[0] == 'Haiku': # Haiku clipboard detection
run(['clipboard', '-c', _mfa_key])
elif Path("/data/data/com.termux").exists(): # Termux (Android) clipboard detection
run(['termux-clipboard-set', _mfa_key])
else: # X11 clipboard detection
run(['xclip', '-sel', 'c'], stdin=Popen(['echo', '-n', _mfa_key], stdout=PIPE).stdout)
sleep(1)
except KeyboardInterrupt:
print('\n')
s_exit(0)
-265
View File
@@ -1,265 +0,0 @@
#!/usr/bin/env bash
version=$(sed -n '1{p;q}' ../version)
if [ -z "$2" ]; then
revision=1
else
revision="$2"
fi
_create_generic() {
echo -e '\npackaging as generic...\n'
mkdir -p output/generictemp/usr/{bin,lib/sshyp,share/man/man1}
cp -r lib/. output/generictemp/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp-mfa.py output/generictemp/usr/bin/sshyp-mfa
cp -r share output/generictemp/usr/
cp extra/manpage output/generictemp/usr/share/man/man1/sshyp-mfa.1
gzip output/generictemp/usr/share/man/man1/sshyp-mfa.1
tar -C output/generictemp -cvJf output/sshyp-mfa-"$version".tar.xz usr/
rm -rf output/generictemp
sha512="$(sha512sum output/sshyp-mfa-"$version".tar.xz | awk '{print $1;}')"
echo -e "\nsha512 sum:\n$sha512"
echo -e "\ngeneric packaging complete\n"
} &&
_create_pkgbuild() {
source='https://github.com/rwinkhart/sshyp-labs/releases/download/v$pkgver/sshyp-mfa-$pkgver.tar.xz'
echo -e '\ngenerating PKGBUILD...'
echo "# Maintainer: Randall Winkhart <idgr at tutanota dot com>
pkgname=sshyp-mfa
pkgver="$version"
pkgrel="$revision"
pkgdesc='An MFA (TOTP/Steam) key generator for the sshyp password manager'
url='https://github.com/rwinkhart/sshyp-labs'
arch=('any')
license=('GPL-3.0-only')
depends=(sshyp)
source=(\""$source"\")
sha512sums=('"$sha512"')
package() {
tar xf sshyp-mfa-"\"\$pkgver\"".tar.xz -C "\"\${pkgdir}\""
}
" > output/PKGBUILD
echo -e "\nPKGBUILD generated\n"
} &&
_create_apkbuild() {
source='https://github.com/rwinkhart/sshyp-labs/releases/download/v$pkgver/sshyp-mfa-$pkgver.tar.xz'
echo -e '\ngenerating APKBUILD...'
echo "# Maintainer: Randall Winkhart <idgr@tutanota.com>
pkgname=sshyp-mfa
pkgver="$version"
pkgrel="$((revision-1))"
pkgdesc='An MFA (TOTP/Steam) key generator for the sshyp password manager'
options=!check
url='https://github.com/rwinkhart/sshyp-labs'
arch='noarch'
license='GPL-3.0-only'
depends='sshyp'
source=\""$source"\"
package() {
mkdir -p "\"\$pkgdir\""
cp -r "\"\$srcdir/usr/"\" "\"\$pkgdir\""
}
sha512sums=\"
"$sha512' 'sshyp-mfa-\"\$pkgver\".tar.xz"
\"
" > output/APKBUILD
echo -e "\nAPKBUILD generated\n"
} &&
_create_hpkg() {
echo -e '\npackaging for Haiku...\n'
mkdir -p output/haikutemp/{bin,lib/sshyp,documentation/{man/man1,packages/sshyp-mfa}}
echo "name sshypmfa
version "$version"-"$revision"
architecture any
summary \"An MFA (TOTP/Steam) key generator for the sshyp password manager\"
description \"sshyp-mfa is an extension for the sshyp password manager that reads MFA data from sshyp entries and generates generic TOTP and Steam keys.\"
packager \"Randall Winkhart <idgr at tutanota dot com>\"
vendor \"Randall Winkhart\"
licenses {
\"GNU GPL v3\"
}
copyrights {
\"2021-2022 Randall Winkhart\"
}
provides {
sshypmfa = "$version"
cmd:sshypmfa
}
requires {
sshyp
}
urls {
\"https://github.com/rwinkhart/sshyp-labs\"
}
" > output/haikutemp/.PackageInfo
cp -r lib/. output/haikutemp/lib/sshyp/
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' output/haikutemp/lib/sshyp/sshyp-mfa.py
ln -s /system/lib/sshyp/sshyp-mfa.py output/haikutemp/bin/sshyp-mfa
cp -r share/licenses/sshyp-mfa/. output/haikutemp/documentation/packages/sshyp-mfa/
cp extra/manpage output/haikutemp/documentation/man/man1/sshyp-mfa.1
gzip output/haikutemp/documentation/man/man1/sshyp-mfa.1
cd output/haikutemp
package create -b sshyp-mfa-"$version"-"$revision"_all.hpkg
package add sshyp-mfa-"$version"-"$revision"_all.hpkg bin lib documentation
cd ../..
mv output/haikutemp/sshyp-mfa-"$version"-"$revision"_all.hpkg output/
rm -rf output/haikutemp
echo -e "\nHaiku packaging complete\n"
} &&
_create_deb() {
echo -e '\npackaging for Debian/Ubuntu...\n'
mkdir -p output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/{DEBIAN,usr/{lib/sshyp,bin,share/man/man1}}
echo "Package: sshyp-mfa
Version: $version
Section: utils
Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: An MFA (TOTP/Steam) key generator for the sshyp password manager
Depends: sshyp
Priority: optional
Installed-Size: 100
" > output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/DEBIAN/control
cp -r lib/. output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp-mfa.py output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/usr/bin/sshyp-mfa
cp -r share output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/usr/
cp extra/manpage output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/usr/share/man/man1/sshyp-mfa.1
gzip output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/usr/share/man/man1/sshyp-mfa.1
dpkg-deb --build --root-owner-group output/debiantemp/sshyp-mfa_"$version"-"$revision"_all/
mv output/debiantemp/sshyp-mfa_"$version"-"$revision"_all.deb output/
rm -rf output/debiantemp
echo -e "\nDebian/Ubuntu packaging complete\n"
} &&
_create_termux() {
echo -e '\npackaging for Termux...\n'
mkdir -p output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/{DEBIAN,data/data/com.termux/files/usr/{lib/sshyp,bin,share/man/man1}}
echo "Package: sshyp-mfa
Version: $version
Section: utils
Architecture: all
Maintainer: Randall Winkhart <idgr at tutanota dot com>
Description: An MFA (TOTP/Steam) key generator for the sshyp password manager
Depends: sshyp
Priority: optional
Installed-Size: 100
" > output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/DEBIAN/control
cp -r lib/. output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/lib/sshyp/
ln -s /data/data/com.termux/files/usr/lib/sshyp/sshyp-mfa.py output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/bin/sshyp-mfa
cp -r share output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/
cp extra/manpage output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp-mfa.1
gzip output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux/data/data/com.termux/files/usr/share/man/man1/sshyp-mfa.1
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/
rm -rf output/termuxtemp
echo -e "\nTermux packaging complete\n"
} &&
_create_rpm() {
echo -e '\npackaging for Fedora...\n'
rm -rf ~/rpmbuild
rpmdev-setuptree
cp output/sshyp-mfa-"$version".tar.xz ~/rpmbuild/SOURCES
echo "Name: sshyp-mfa
Version: "$version"
Release: "$revision"
Summary: An MFA (TOTP/Steam) key generator for the sshyp password manager
BuildArch: noarch
License: GPL-3.0-only
URL: https://github.com/rwinkhart/sshyp-labs
Source0: sshyp-mfa-"$version".tar.xz
Requires: sshyp
%description
sshyp-mfa is an extension for the sshyp password manager that reads MFA data from sshyp entries and generates generic TOTP and Steam keys.
%install
tar xf %{_sourcedir}/sshyp-mfa-"$version".tar.xz -C %{_sourcedir}
cp -r %{_sourcedir}/usr %{buildroot}
%files
/usr/bin/sshyp-mfa
/usr/lib/sshyp/sshyp-mfa.py
/usr/lib/sshyp/extensions/sshyp-mfa
%license /usr/share/licenses/sshyp-mfa/license
%doc /usr/share/man/man1/sshyp-mfa.1.gz
" > ~/rpmbuild/SPECS/sshyp-mfa.spec
rpmbuild -bb ~/rpmbuild/SPECS/sshyp-mfa.spec
mv ~/rpmbuild/RPMS/noarch/* output/
rm -rf ~/rpmbuild
echo -e "\nFedora packaging complete\n"
} &&
_create_freebsd_pkg() {
echo -e '\npackaging for FreeBSD...\n'
mkdir -p output/freebsdtemp/usr/{lib/sshyp,bin,share/man/man1}
echo "name: sshyp-mfa
version: \""$version"\"
abi = \"FreeBSD:13:*\";
arch = \"freebsd:13:*\";
origin: security/sshyp-mfa
comment: \"a sshyp extension\"
desc: \"an MFA (TOTP/Steam) key generator for the sshyp password manager\"
maintainer: <idgr at tutanota dot com>
www: https://github.com/rwinkhart/sshyp-labs
prefix: /
\"deps\" : {
\"sshyp\" : {
\"origin\" : \"security/sshyp\"
},
},
" > output/freebsdtemp/+MANIFEST
echo "/usr/bin/sshyp-mfa
/usr/lib/sshyp/sshyp-mfa.py
/usr/lib/sshyp/extensions/sshyp-mfa
/usr/share/licenses/sshyp-mfa/license
/usr/share/man/man1/sshyp-mfa.1.gz
" > output/freebsdtemp/plist
cp -r lib/. output/freebsdtemp/usr/lib/sshyp/
ln -s /usr/lib/sshyp/sshyp-mfa.py output/freebsdtemp/usr/bin/sshyp-mfa
cp -r share output/freebsdtemp/usr/
cp extra/manpage output/freebsdtemp/usr/share/man/man1/sshyp-mfa.1
gzip output/freebsdtemp/usr/share/man/man1/sshyp-mfa.1
pkg create -m output/freebsdtemp/ -r output/freebsdtemp/ -p output/freebsdtemp/plist -o output/
rm -rf output/freebsdtemp
echo -e "\nFreeBSD packaging complete\n"
} &&
if [ "$1" == "generic" ]; then # build scripts
_create_generic
elif [ "$1" == "pkgbuild" ]; then
_create_generic
_create_pkgbuild
elif [ "$1" == "apkbuild" ]; then
_create_generic
_create_apkbuild
elif [ "$1" == "haiku" ]; then # distribution packages
_create_hpkg
elif [ "$1" == "debian" ]; then
_create_deb
elif [ "$1" == "termux" ]; then
_create_termux
elif [ "$1" == "fedora" ]; then
_create_generic
_create_rpm
elif [ "$1" == "freebsd" ]; then
_create_freebsd_pkg
elif [ "$1" == "buildable-arch" ]; then
_create_generic
_create_pkgbuild
_create_apkbuild
if [[ $(pacman -Q dpkg) == "dpkg"* ]]; then
_create_deb
_create_termux
fi
if [[ "$(pacman -Q freebsd-pkg)" == "freebsd-pkg"* ]]; then
_create_freebsd_pkg
fi
else
echo -e '\nusage: package.sh [target] <revision>\n\ntargets:\n mainline: pkgbuild apkbuild haiku fedora debian\n experimental: freebsd termux\n other: buildable-arch\n'
fi
-1
View File
@@ -1 +0,0 @@
steam[client]
@@ -1,14 +0,0 @@
sshyp-mfa is a FOSS extension for the sshyp password manager.
Copyright (C) 2022-2023 Randall Winkhart idgr@tutanota.com
This program is free software: you can redistribute it and/or modify
it under the terms of version 3 (only) of the GNU General Public License
as published by the Free Software Foundation.
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 <http://www.gnu.org/licenses/>.
-1
View File
@@ -1 +0,0 @@
1.4.0.1