From a144b2769de1683495e0a84d2d6b287c86e7ec0c Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sun, 4 May 2025 18:07:02 -0400 Subject: [PATCH] Port to latest libmutton development version & begin migration from GPG to RCW --- .gitignore | 2 ++ go.mod | 7 ++++++- go.sum | 22 ++++++---------------- mutn.go | 4 ++-- src/cli/1globals.go | 2 +- src/cli/edit.go | 8 ++++---- src/cli/entryReader.go | 4 ++-- src/cli/init.go | 33 ++++++++++++++++----------------- src/cli/utilitiesMisc.go | 18 +++++++++++++++++- 9 files changed, 56 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index b61f2d8..8f7ed3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /mutn /mutn.exe +/MUTN +/MUTN.exe /main /main.exe /version.go diff --git a/go.mod b/go.mod index 316229d..91ab07f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,8 @@ go 1.24.2 require ( github.com/Trojan2021/BEAN v0.0.0-20241210230804-8f294833b514 github.com/charmbracelet/glamour v0.7.0 - github.com/rwinkhart/libmutton v0.3.1 + github.com/rwinkhart/libmutton v0.3.2-0.20250504220509-826d94bc4397 + github.com/rwinkhart/rcw v0.0.0-20250504183741-f176d57b6ba5 golang.org/x/term v0.31.0 ) @@ -42,3 +43,7 @@ require ( golang.org/x/sys v0.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) + +replace golang.org/x/sys => github.com/rwinkhart/sys-freebsd-13-xucred v0.32.0 + +replace github.com/Microsoft/go-winio => github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410 diff --git a/go.sum b/go.sum index 8c0949f..fbf89b2 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,12 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rwinkhart/convertroman v0.2.0 h1:otUm939eXT77q/Lr31mJtMwVWEw0RjEUB71gO65h9OM= github.com/rwinkhart/convertroman v0.2.0/go.mod h1:Af6HqvX0EIM4Y3HcnNXbbRey1dLasGB7WU0+3Cowgmw= -github.com/rwinkhart/libmutton v0.3.1 h1:VCaviTYLO8dif75ZjOJh1EZoaM2zH5U1CaYOa0qkW/E= -github.com/rwinkhart/libmutton v0.3.1/go.mod h1:bI03SUAheTefZYcEv19Vg7hWsEamd3qjqTqlB/Z6YSw= +github.com/rwinkhart/libmutton v0.3.2-0.20250504220509-826d94bc4397 h1:+Zlv62ZB7QTIFDNltMj6rm+7huo05N99BBWhoyVVJQc= +github.com/rwinkhart/libmutton v0.3.2-0.20250504220509-826d94bc4397/go.mod h1:9a4EYsPTLVI4Je7ql3jsMz7B9JP4PaRZSuTR/BctBIU= +github.com/rwinkhart/rcw v0.0.0-20250504183741-f176d57b6ba5 h1:BX31d76TLGZqeukYECLFPkLD+SS/CAV12Lkdlc6UuGM= +github.com/rwinkhart/rcw v0.0.0-20250504183741-f176d57b6ba5/go.mod h1:giXrq9o5a7bwMSuMvz+5bb2+qbM+zV4pXaG/SceXfLM= +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= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -111,20 +115,6 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/mutn.go b/mutn.go index 27f3635..d42f235 100644 --- a/mutn.go +++ b/mutn.go @@ -37,7 +37,7 @@ func main() { case "show", "-s": cli.EntryReaderDecrypt(targetLocation, false) case "copy": - core.CopyArgument(targetLocation, 0) + core.CopyArgument(targetLocation, 0, cli.GetRCWPassphrase()) case "edit": cli.EditEntryField(targetLocation, true, 0) case "gen": @@ -81,7 +81,7 @@ func main() { default: cli.HelpCopy() } - core.CopyArgument(targetLocation, field) + core.CopyArgument(targetLocation, field, cli.GetRCWPassphrase()) case "edit": var field int // indicates which (numbered) field to edit switch args[3] { diff --git a/src/cli/1globals.go b/src/cli/1globals.go index ec4340d..c9487af 100644 --- a/src/cli/1globals.go +++ b/src/cli/1globals.go @@ -11,7 +11,7 @@ var ( ) const ( - MUTNVersion = "0.2.6" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1" + MUTNVersion = "0.D.0" // untagged releases feature a letter suffix corresponding to the eventual release version, e.g "0.2.A" -> "0.2.0", "0.2.B" -> "0.2.1" AnsiBold = "\033[1m" ansiBlackOnWhite = "\033[38;5;0;48;5;15m" diff --git a/src/cli/edit.go b/src/cli/edit.go index 4818af2..83a6b9b 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -22,16 +22,16 @@ func RenameCli(oldLocationIncomplete string) { // EditEntryField edits a field of an entry at targetLocation (user input). func EditEntryField(targetLocation string, hideSecrets bool, field int) { // fetch old entry data (with all required lines present) - unencryptedEntry := core.GetOldEntryData(targetLocation, field) + unencryptedEntry := core.GetOldEntryData(targetLocation, field, GetRCWPassphrase()) // edit the field switch field { case 0: - unencryptedEntry[field] = string(inputHidden("Password:")) + unencryptedEntry[field] = string(GetRCWPassphrase()) case 1: unencryptedEntry[field] = input("Username:") case 2: - unencryptedEntry[field] = string(inputHidden("TOTP secret:")) + unencryptedEntry[field] = string(GetRCWPassphrase()) case 3: unencryptedEntry[field] = input("URL:") case 4: // edit notes fields @@ -54,7 +54,7 @@ func EditEntryField(targetLocation string, hideSecrets bool, field int) { // GenUpdate generates a new password for an entry at targetLocation (user input). func GenUpdate(targetLocation string, hideSecrets bool) { // fetch old entry data - unencryptedEntry := core.GetOldEntryData(targetLocation, 0) + unencryptedEntry := core.GetOldEntryData(targetLocation, 0, GetRCWPassphrase()) // generate a new password unencryptedEntry[0] = inputPasswordGen() diff --git a/src/cli/entryReader.go b/src/cli/entryReader.go index f0914d9..0a9169f 100644 --- a/src/cli/entryReader.go +++ b/src/cli/entryReader.go @@ -68,10 +68,10 @@ func EntryReader(decryptedEntry []string, hideSecrets, syncEnabled bool) { os.Exit(0) } -// EntryReaderDecrypt is a wrapper for EntryReader that first decrypts a GPG-encrypted file before sending it to EntryReader. +// EntryReaderDecrypt is a wrapper for EntryReader that first decrypts an RCW-wrapped file before sending it to EntryReader. func EntryReaderDecrypt(targetLocation string, hideSecrets bool) { if isFile, _ := core.TargetIsFile(targetLocation, true, 2); isFile { - EntryReader(core.DecryptGPG(targetLocation), hideSecrets, false) // never sync if decrypting straight to EntryReader, as this means the entry could not have been modified + EntryReader(core.DecryptFileToSlice(targetLocation, GetRCWPassphrase()), hideSecrets, false) // never sync if decrypting straight to EntryReader, as this means the entry could not have been modified } // do not exit, as this is the job of EntryReader } diff --git a/src/cli/init.go b/src/cli/init.go index 805984b..d941122 100644 --- a/src/cli/init.go +++ b/src/cli/init.go @@ -1,6 +1,7 @@ package cli import ( + "bytes" "cmp" "fmt" "os" @@ -12,21 +13,7 @@ import ( // TempInitCli initializes the MUTN environment based on user input. func TempInitCli() { - // gpgID - var gpgID string - if inputBinary("Auto-generate GPG key?") { - gpgID = core.GpgKeyGen() - } else { - // select GPG key from menu - uidSlice := core.GpgUIDListGen() - gpgIDInt := inputMenuGen("Select GPG key:", uidSlice) - if gpgIDInt == 0 { - core.PrintError("No GPG keys found - please generate one", core.ErrorTargetNotFound, true) - } - gpgID = uidSlice[gpgIDInt-1] - } - - // textEditor + // text editor textEditor := cmp.Or(input("Text editor (leave blank for $EDITOR, falls back to \""+fallbackEditor+"\"):"), os.Getenv("EDITOR"), fallbackEditor) // SSH info @@ -56,7 +43,7 @@ func TempInitCli() { oldDeviceID := core.DirInit(false) // write config file (temporarily assigns sshEntryRoot and sshIsWindows to null to pass initial device ID registration) - core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}, nil, false) + core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "sshUser", sshUser}, {"LIBMUTTON", "sshIP", sshIP}, {"LIBMUTTON", "sshPort", sshPort}, {"LIBMUTTON", "sshKey", sshKey}, {"LIBMUTTON", "sshKeyProtected", strconv.FormatBool(sshKeyProtected)}, {"LIBMUTTON", "sshEntryRoot", "null"}, {"LIBMUTTON", "sshIsWindows", "false"}}, nil, false) // generate and register device ID sshEntryRoot, sshIsWindows := sync.DeviceIDGen(oldDeviceID) @@ -68,6 +55,18 @@ func TempInitCli() { core.DirInit(false) // write config file - core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}, {"LIBMUTTON", "gpgID", gpgID}}, nil, false) + core.WriteConfig([][3]string{{"MUTN", "textEditor", textEditor}}, nil, false) + } + // RCW sanity check file + var passphraseInput1 []byte + for { + passphraseInput1 = inputHidden("New master (RCW) passphrase:") + passphraseInput2 := inputHidden("Confirm RCW passphrase:") + if !bytes.Equal(passphraseInput1, passphraseInput2) { + fmt.Println(core.AnsiError + "Passphrases do not match" + core.AnsiReset) + continue + } + core.RCWSanityCheckGen(passphraseInput1) + break } } diff --git a/src/cli/utilitiesMisc.go b/src/cli/utilitiesMisc.go index f786e15..f04eb00 100644 --- a/src/cli/utilitiesMisc.go +++ b/src/cli/utilitiesMisc.go @@ -8,9 +8,24 @@ import ( "github.com/rwinkhart/libmutton/core" "github.com/rwinkhart/libmutton/sync" + "github.com/rwinkhart/rcw/wrappers" "golang.org/x/term" ) +// GetRCWPassphrase retrieves the RCW passphrase from the user. +// TODO attempt to retrieve from RCW daemon +func GetRCWPassphrase() []byte { + for { + passphrase := inputHidden("RCW Passphrase:") + err := wrappers.RunSanityCheck(core.ConfigDir+"/sanity.rcw", passphrase) + if err != nil { + fmt.Println(core.AnsiError + "Failed to encrypt - " + err.Error() + core.AnsiReset) + continue + } + return passphrase + } +} + // input prompts the user for input and returns the input as a string. func input(prompt string) string { fmt.Print("\n" + prompt + " ") @@ -85,7 +100,8 @@ func inputPasswordGen() string { func writeEntryCLI(targetLocation string, unencryptedEntry []string, hideSecrets bool) { if core.EntryIsNotEmpty(unencryptedEntry) { // write the entry to the target location - core.WriteEntry(targetLocation, unencryptedEntry) + joinedUnencryptedEntry := strings.Join(unencryptedEntry, "\n") + core.WriteEntry(targetLocation, []byte(joinedUnencryptedEntry), GetRCWPassphrase()) // preview the entry fmt.Println(AnsiBold + "\nEntry Preview:" + core.AnsiReset) EntryReader(unencryptedEntry, hideSecrets, true)