mirror of
https://github.com/rwinkhart/libmutton.git
synced 2026-08-27 20:36:29 -04:00
Port to latest rcw development version
This commit is contained in:
@@ -33,3 +33,5 @@ const (
|
||||
ErrorClipboard = 110
|
||||
ErrorOther = 111
|
||||
)
|
||||
|
||||
var GetPassphrase func() []byte
|
||||
|
||||
+2
-2
@@ -11,10 +11,10 @@ import (
|
||||
)
|
||||
|
||||
// CopyArgument copies a field from an entry to the clipboard.
|
||||
func CopyArgument(targetLocation string, field int, passphrase []byte) {
|
||||
func CopyArgument(targetLocation string, field int) {
|
||||
if isFile, _ := TargetIsFile(targetLocation, true, 2); isFile {
|
||||
|
||||
decryptedEntry := DecryptFileToSlice(targetLocation, passphrase)
|
||||
decryptedEntry := DecryptFileToSlice(targetLocation)
|
||||
var copySubject string // will store data to be copied
|
||||
|
||||
// ensure field exists in entry
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
package core
|
||||
|
||||
// GetOldEntryData decrypts and returns old entry data (with all required lines present).
|
||||
func GetOldEntryData(targetLocation string, field int, passphrase []byte) []string {
|
||||
func GetOldEntryData(targetLocation string, field int) []string {
|
||||
// ensure targetLocation exists
|
||||
TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := DecryptFileToSlice(targetLocation, passphrase)
|
||||
unencryptedEntry := DecryptFileToSlice(targetLocation)
|
||||
|
||||
// return the old entry data with all required lines present
|
||||
if field > 0 {
|
||||
|
||||
+51
-31
@@ -1,48 +1,68 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rwinkhart/rcw/daemon"
|
||||
"github.com/rwinkhart/rcw/wrappers"
|
||||
)
|
||||
|
||||
// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings.
|
||||
func DecryptFileToSlice(targetLocation string, passphrase []byte) []string {
|
||||
encBytes, err := os.ReadFile(targetLocation)
|
||||
if err != nil {
|
||||
PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true)
|
||||
}
|
||||
decBytes, err := wrappers.Decrypt(encBytes, passphrase)
|
||||
if err != nil {
|
||||
PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true)
|
||||
}
|
||||
return strings.Split(string(decBytes), "\n")
|
||||
}
|
||||
|
||||
// EncryptBytes encrypts a byte slice using RCW and returns the encrypted data.
|
||||
func EncryptBytes(decBytes []byte, passphrase []byte) []byte {
|
||||
encBytes := wrappers.Encrypt(decBytes, passphrase)
|
||||
return encBytes
|
||||
}
|
||||
|
||||
// LaunchRCWDProcess launches an RCW daemon to serve the given passphrase.
|
||||
func LaunchRCWDProcess(passphrase string) {
|
||||
cmd := exec.Command(os.Args[0], "startrcwd")
|
||||
writeToStdin(cmd, passphrase)
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
PrintError("Failed to launch RCW daemon - Does this libmutton implementation support the \"startrcwd\" argument?", ErrorOther, true)
|
||||
}
|
||||
}
|
||||
|
||||
// RCWDArgument reads the passphrase from stdin and serves it via an RCW daemon.
|
||||
// RCWDArgument reads the passphrase from stdin and caches it via an RCW daemon.
|
||||
func RCWDArgument() {
|
||||
passphrase := readFromStdin()
|
||||
if passphrase == "" {
|
||||
os.Exit(0)
|
||||
}
|
||||
daemon.Start(string(passphrase))
|
||||
daemon.Start([]byte(passphrase))
|
||||
}
|
||||
|
||||
// DecryptFileToSlice decrypts an RCW wrapped file and returns the contents as a slice of (trimmed) strings.
|
||||
func DecryptFileToSlice(targetLocation string) []string {
|
||||
// read encrypted file
|
||||
encBytes, err := os.ReadFile(targetLocation)
|
||||
if err != nil {
|
||||
PrintError("Failed to decrypt \""+targetLocation+"\" - "+err.Error(), ErrorDecryption, true)
|
||||
}
|
||||
|
||||
// decrypt data using RCW daemon
|
||||
launchRCWDProcess()
|
||||
return strings.Split(string(daemon.GetDec(encBytes)), "\n")
|
||||
}
|
||||
|
||||
// EncryptBytes encrypts a byte slice using RCW and returns the encrypted data.
|
||||
func EncryptBytes(decBytes []byte) []byte {
|
||||
launchRCWDProcess()
|
||||
return daemon.GetEnc(decBytes)
|
||||
}
|
||||
|
||||
// launchRCWDProcess launches an RCW daemon to cache a passphrase.
|
||||
// It returns immediately if the daemon appears to be already running.
|
||||
func launchRCWDProcess() {
|
||||
if daemon.IsOpen() {
|
||||
return
|
||||
}
|
||||
var passphrase []byte
|
||||
for {
|
||||
passphrase = GetPassphrase()
|
||||
err := wrappers.RunSanityCheck(ConfigDir+PathSeparator+"sanity.rcw", passphrase)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fmt.Println(AnsiError + "Incorrect passphrase" + AnsiReset)
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "startrcwd")
|
||||
writeToStdin(cmd, string(passphrase))
|
||||
cmd.Start()
|
||||
|
||||
// block until socket file is created
|
||||
for {
|
||||
if daemon.IsOpen() {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func TargetIsFile(targetLocation string, errorOnFail bool, failCondition uint8)
|
||||
}
|
||||
|
||||
// WriteEntry writes entryData to an encrypted file at targetLocation.
|
||||
func WriteEntry(targetLocation string, entryData []byte, passphrase []byte) {
|
||||
encryptedBytes := EncryptBytes(entryData, passphrase)
|
||||
func WriteEntry(targetLocation string, entryData []byte) {
|
||||
encryptedBytes := EncryptBytes(entryData)
|
||||
err := os.WriteFile(targetLocation, encryptedBytes, 0600)
|
||||
if err != nil {
|
||||
PrintError("Failed to write to file: "+err.Error(), ErrorWrite, true)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
module github.com/rwinkhart/libmutton
|
||||
|
||||
go 1.24.2
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/fortis/go-steam-totp v0.0.0-20171114202746-18e928674727
|
||||
github.com/pkg/sftp v1.13.9
|
||||
github.com/pquerna/otp v1.4.1-0.20231130234153-3357de7c0481
|
||||
github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68
|
||||
github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43
|
||||
golang.design/x/clipboard v0.7.0 // only for Android builds
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect; only for Android builds
|
||||
golang.org/x/image v0.26.0 // indirect; only for Android builds
|
||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect; only for Android builds
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
|
||||
replace golang.org/x/sys => github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0
|
||||
|
||||
@@ -19,8 +19,8 @@ github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e84
|
||||
github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea h1:VE2ti/AE4Y3kgnyK+J5c5hpddE+dKHpsFch3K2R6puQ=
|
||||
github.com/rwinkhart/peercred-mini v0.0.0-20250407033241-c09add2eceea/go.mod h1:t+YkvAdnTKTrg4d469tw3K+GCUzX/Bja4h8yKjSIsGs=
|
||||
github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68 h1:2+mAXmmlt28JyyCdKih3UN4QWE8aZu2hzos8jH8J8cU=
|
||||
github.com/rwinkhart/rcw v0.0.0-20250505000323-5975c71b4f68/go.mod h1:giXrq9o5a7bwMSuMvz+5bb2+qbM+zV4pXaG/SceXfLM=
|
||||
github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43 h1:TFHU8EkCP0AxAExfA/k5TLfFt7Nja2AmPA1DdVzgRX8=
|
||||
github.com/rwinkhart/rcw v0.0.0-20250508202611-f447c8a28b43/go.mod h1:6/TBegxRkK+FbGEmcxqDs4entuCX45cqpYOYKUv2MI0=
|
||||
github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 h1:KRbqimv9Eexf3VB2FrRAQ4v2fGGu7gt3ayMgdT0FNao=
|
||||
github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -38,8 +38,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
@@ -75,8 +75,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
+3
-2
@@ -18,8 +18,9 @@ These are as follows:
|
||||
## Required Global Variable Manipulation
|
||||
libmutton provides a `PassphraseInputFunction` global variable that all clients must set to support passphrase-protected SSH identity files. This approach allows for different types of clients (CLI, GUI, TUI) to prompt for the passphrase in the most appropriate way.
|
||||
|
||||
## Required Argument (clipclear)
|
||||
The `clipclear` argument should be accepted by all non-interactive CLI libmutton implementations (not required for interactive GUI/TUI implementations). In order to clear the clipboard on a timer, non-interactive libmutton-based password managers call another instance of their executable with the `clipclear` argument (e.g. `mutn clipclear`) with the intended clipboard contents provided via STDIN. If after 30 seconds the clipboard contents have not changed, they are cleared. Please accept a `clipclear` argument that calls `core.ClipClearArgument()`.
|
||||
## Required Arguments
|
||||
- `clipclear`: Should be accepted by all non-interactive CLI libmutton implementations (not required for interactive GUI/TUI implementations). In order to clear the clipboard on a timer, non-interactive libmutton-based password managers call another instance of their executable with the `clipclear` argument (e.g. `mutn clipclear`) with the intended clipboard contents provided via STDIN. If after 30 seconds the clipboard contents have not changed, they are cleared. Please accept a `clipclear` argument that calls `core.ClipClearArgument()`.
|
||||
- `startrcwd`: Should be accepted by all libmutton implementations making use of the RCW daemon to cache passphrases. Please accept a `startrcwd` argument that calls `core.RCWDArgument()`.
|
||||
|
||||
## Configuration
|
||||
libmutton-based password manager clients should all share the same INI configuration file.
|
||||
|
||||
Reference in New Issue
Block a user