35 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
42 changed files with 569 additions and 33 deletions
+4 -1
View File
@@ -1 +1,4 @@
*
/extra/conversion-scripts/gpgToRCW/1output
/extra/conversion-scripts/gpgToRCW/gpgToRCW
/extra/conversion-scripts/upgradeRCW/1output
/extra/conversion-scripts/upgradeRCW/upgradeRCW
+8 -4
View File
@@ -1,7 +1,7 @@
# sshyp-labs
Extensions for the [sshyp password manager](https://github.com/rwinkhart/sshyp).
When new functionality is desired that goes outside of sshyp's primary goals or requires venturing outside of the Python standard library, said functionality is implemented as an extension.
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.
@@ -9,11 +9,15 @@ 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).
# 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)
[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](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)
# 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 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
View File
@@ -1,4 +0,0 @@
#!/bin/sh
git add -f LICENSE README.md commit.sh extensions extra pointers .gitignore
git commit -m "$1"
git push
@@ -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)
+4 -4
View File
@@ -1,6 +1,6 @@
#!/bin/sh
version='1.5.0.2'
version='1.5.1.3'
if [ -z "$2" ]; then
revision=1
else
@@ -34,8 +34,8 @@ urls {
}
" > output/haikutemp/.PackageInfo
cp ./sshyp-mfa.py output/haikutemp/lib/sshyp/sshyp-mfa
cp ./sshyp-mfa.ini output/haikutemp/lib/sshyp/extensions/
sed -i '1 s/.*/#!\/bin\/env\ python3.10/' 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
@@ -60,7 +60,7 @@ 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
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/
mv output/termuxtemp/sshyp-mfa_"$version"-"$revision"_all_termux.deb output/TERMUX-sshyp-mfa_"$version"-"$revision"_all.deb
rm -rf output/termuxtemp
+27 -19
View File
@@ -1,9 +1,11 @@
#!/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
@@ -11,25 +13,37 @@ home = expanduser("~")
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
_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 == 'true':
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]))
_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:
@@ -55,7 +69,7 @@ if __name__ == '__main__':
sshyp_data = ConfigParser()
sshyp_data.read(f"{home}/.config/sshyp/sshyp.ini")
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
try:
@@ -67,18 +81,13 @@ if __name__ == '__main__':
if copied is None:
copied = 1
if mfa_data[0] == 'steam':
try:
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)
_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))
run(('powershell.exe', '-c', "Set-Clipboard '" + _mfa_key + "'"))
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
run(('clipboard', '-c', _mfa_key))
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)
sleep(1)
except KeyboardInterrupt:
print('\n')
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.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