From 67bf6e42b9c623f24d9547d822ea4d2fd45002d1 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Tue, 21 May 2024 19:21:42 -0400 Subject: [PATCH] Move TOTP secrets to a dedicated entry field (breaks sshyp entry format compatibility) --- extra/completion.bash | 2 +- extra/completion.ps1 | 6 +++--- extra/completion.zsh | 2 +- mutn.go | 16 +++++++++------- src/backend/copy.go | 8 ++++---- src/backend/edit.go | 2 +- src/backend/gpg.go | 2 +- src/cli/add.go | 7 ++++--- src/cli/edit.go | 8 +++++--- src/cli/entryReader.go | 15 ++++++++++++--- src/cli/printInfo.go | 10 ++++++---- 11 files changed, 47 insertions(+), 31 deletions(-) diff --git a/extra/completion.bash b/extra/completion.bash index 87dde34..42fe1ae 100644 --- a/extra/completion.bash +++ b/extra/completion.bash @@ -31,7 +31,7 @@ _mutnCompletions() { while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "password username totp url note" -- "$cur" ) ;; edit ) - while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "password username url note rename" -- "$cur" ) + while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "password username totp url note rename" -- "$cur" ) ;; gen ) while read -r; do COMPREPLY+=( "$REPLY" ); done < <( compgen -W "update" -- "$cur" ) diff --git a/extra/completion.ps1 b/extra/completion.ps1 index 2ef054e..0f26b84 100644 --- a/extra/completion.ps1 +++ b/extra/completion.ps1 @@ -7,9 +7,9 @@ function cliMUTNEntryCompleter { (Get-ChildItem -Path $mutnPath -Recurse -File).FullName.Substring($mutnPath.Length) -replace '\\', '/' -replace ' ', '` ' } } catch { - $trimmedPaths = $null # If any errors occur (especially, "You cannot call a method on a null-valued expression", set $trimmedPaths to $null + $trimmedPaths = $null # if any errors occur (especially, "You cannot call a method on a null-valued expression", set $trimmedPaths to $null } - if ($null -eq $trimmedPaths) { # If no entries are found, add 'help' to $trimmedPaths + if ($null -eq $trimmedPaths) { # if no entries are found, add 'help' to $trimmedPaths $trimmedPaths = 'help' } $trimmedPaths | Where-Object { $_ -like "$wordToComplete*" } @@ -21,7 +21,7 @@ function cliMUTNOptionCompleter { $possibleValues = @{ add = @('password', 'note', 'folder') copy = @('password', 'username', 'totp', 'url', 'note') - edit = @('password', 'username', 'url', 'note', 'rename') + edit = @('password', 'username', 'totp', 'url', 'note', 'rename') gen = @('update') } diff --git a/extra/completion.zsh b/extra/completion.zsh index 4315d87..3746049 100644 --- a/extra/completion.zsh +++ b/extra/completion.zsh @@ -23,7 +23,7 @@ case ${words[-2]} in compadd {password,username,totp,url,note} ;; edit ) - compadd {password,username,url,note,rename} + compadd {password,username,totp,url,note,rename} ;; gen ) compadd update diff --git a/mutn.go b/mutn.go index 0f05f07..acf9d2c 100644 --- a/mutn.go +++ b/mutn.go @@ -72,13 +72,13 @@ func main() { field = 0 case "username", "-u": field = 1 - case "url", "-l": - field = 2 - case "note", "-n": - field = 3 case "totp", "-t": fmt.Println("TOTP code will be copied to clipboard - your clipboard will be kept up to date with the current TOTP code until this process is closed") - field = 5 // TODO Update field after removed from notes (breaking sshyp entry compatibility) + field = 2 + case "url", "-l": + field = 3 + case "note", "-n": + field = 4 default: cli.HelpCopy() } @@ -90,10 +90,12 @@ func main() { field = 0 case "username", "-u": field = 1 - case "url", "-l": + case "totp", "-t": field = 2 - case "note", "-n": + case "url", "-l": field = 3 + case "note", "-n": + field = 4 case "rename", "-r": cli.RenameCli(targetLocation) default: diff --git a/src/backend/copy.go b/src/backend/copy.go index 6794c3a..cb3cf75 100644 --- a/src/backend/copy.go +++ b/src/backend/copy.go @@ -26,17 +26,17 @@ func CopyArgument(targetLocation string, field int, executableName string) { os.Exit(1) } - if field != 5 { // TODO Update field after removed from notes (breaking sshyp entry compatibility) + if field != 2 { copySubject = decryptedEntry[field] } else { // TOTP mode var secret string // stores secret for TOTP generation var forSteam bool // indicates whether to generate TOTP in Steam format - if strings.HasPrefix(decryptedEntry[5], "steam@") { - secret = decryptedEntry[5][6:] + if strings.HasPrefix(decryptedEntry[2], "steam@") { + secret = decryptedEntry[2][6:] forSteam = true } else { - secret = decryptedEntry[5] + secret = decryptedEntry[2] } for { // keep field copied to clipboard, refresh on 30-second intervals diff --git a/src/backend/edit.go b/src/backend/edit.go index 09e3155..714a49f 100644 --- a/src/backend/edit.go +++ b/src/backend/edit.go @@ -36,7 +36,7 @@ func Rename(oldLocation string, newLocation string) { fmt.Println(AnsiError + "Failed to rename - does the target containing directory exists?" + AnsiReset) } - // TODO If in online mode, rename oldLocation to newLocation on the server + // TODO implement synced renaming os.Exit(0) } diff --git a/src/backend/gpg.go b/src/backend/gpg.go index 6ad1f33..24edb69 100644 --- a/src/backend/gpg.go +++ b/src/backend/gpg.go @@ -8,7 +8,7 @@ import ( ) // TODO GPG support is a temporary feature - it will be replaced with a different encryption scheme in the future -// TODO These functions may continue to exist after that point, but consider them deprecated +// These functions may continue to exist after that point, but consider them deprecated // DecryptGPG decrypts a GPG-encrypted file and returns the contents as a slice of (trimmed) strings func DecryptGPG(targetLocation string) []string { diff --git a/src/cli/add.go b/src/cli/add.go index 5aaa525..e7c0ddb 100644 --- a/src/cli/add.go +++ b/src/cli/add.go @@ -28,16 +28,17 @@ func AddEntry(targetLocation string, hidePassword bool, entryType uint8) { password = backend.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2) } + totp := inputHidden("TOTP secret:") url := input("URL:") if inputBinary("Add a note to this entry?") { note, _ := editNote([]string{}) - unencryptedEntry = append([]string{password, username, url}, note...) + unencryptedEntry = append([]string{password, username, totp, url}, note...) } else { - unencryptedEntry = []string{password, username, url} + unencryptedEntry = []string{password, username, totp, url} } } else { note, _ := editNote([]string{}) - unencryptedEntry = append([]string{"", "", ""}, note...) + unencryptedEntry = append([]string{"", "", "", ""}, note...) } // write and preview the new entry diff --git a/src/cli/edit.go b/src/cli/edit.go index 0b8d384..236d29f 100644 --- a/src/cli/edit.go +++ b/src/cli/edit.go @@ -34,11 +34,13 @@ func EditEntryField(targetLocation string, hidePassword bool, field int) { case 1: unencryptedEntry[field] = input("Username:") case 2: + unencryptedEntry[field] = inputHidden("TOTP secret:") + case 3: unencryptedEntry[field] = input("URL:") - case 3: // edit notes fields + case 4: // edit notes fields // store note and non-note data separately - nonNoteData := unencryptedEntry[:3] - noteData := unencryptedEntry[3:] + nonNoteData := unencryptedEntry[:4] + noteData := unencryptedEntry[4:] // edit the note editedNote, noteEdited := editNote(noteData) diff --git a/src/cli/entryReader.go b/src/cli/entryReader.go index 5196b6e..7d431bc 100644 --- a/src/cli/entryReader.go +++ b/src/cli/entryReader.go @@ -34,17 +34,26 @@ func EntryReader(decryptedEntry []string, hidePassword bool, syncEnabled bool) { fmt.Print(ansiDirectoryHeader + "Username:" + backend.AnsiReset + "\n" + decryptedEntry[1] + "\n\n") } case 2: - // if the third field (url) is not empty, print it + // if the third field (TOTP secret) is not empty, print it if decryptedEntry[2] != "" { - fmt.Print(ansiDirectoryHeader + "URL:" + backend.AnsiReset + "\n" + decryptedEntry[2] + "\n\n") + if !hidePassword { + fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + backend.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[2] + backend.AnsiReset + "\n\n") + } else { + fmt.Print(ansiDirectoryHeader + "TOTP Secret:" + backend.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + backend.AnsiReset + "\n\n") + } } case 3: + // if the fourth field (url) is not empty, print it + if decryptedEntry[3] != "" { + fmt.Print(ansiDirectoryHeader + "URL:" + backend.AnsiReset + "\n" + decryptedEntry[3] + "\n\n") + } + case 4: // print the notes header fmt.Println(ansiDirectoryHeader + "Notes:" + backend.AnsiReset) // combine remaining fields into a single string (for markdown rendering) var markdownNotes []string - for field := 3; field < len(decryptedEntry); field++ { + for field := 4; field < len(decryptedEntry); field++ { markdownNotes = append(markdownNotes, decryptedEntry[field]) } r, _ := glamour.NewTermRenderer(glamour.WithStylesFromJSONBytes(glamourStyle()), glamour.WithPreservedNewLines(), glamour.WithWordWrap(width)) diff --git a/src/cli/printInfo.go b/src/cli/printInfo.go index fd2f124..2db2b7d 100644 --- a/src/cli/printInfo.go +++ b/src/cli/printInfo.go @@ -37,12 +37,13 @@ This program comes with absolutely no warranty; type "mutn version" for details. password|-pw| Copy the password of an entry to your clipboard username|-u Copy the username of an entry to your clipboard totp|-t Copy the TOTP code of an entry to your clipboard - url|-l Copy the url of an entry to your clipboard + url|-l Copy the URL of an entry to your clipboard note|-n Copy the note of an entry to your clipboard edit: password|-pw| Change the password of an entry username|-u Change the username of an entry - url|-l Change the url attached to an entry + totp|-t Change the TOTP secret of an entry + url|-l Change the URL attached to an entry note|-n Change the note attached to an entry rename|-r Rename or relocate an entry gen: @@ -77,7 +78,8 @@ func HelpEdit() { edit: password|-pw| Change the password of an entry username|-u Change the username of an entry - url|-l Change the url attached to an entry + totp|-t Change the TOTP secret of an entry + url|-l Change the URL attached to an entry note|-n Change the note attached to an entry rename|-r Rename or relocate an entry` + "\n\n") os.Exit(0) @@ -91,7 +93,7 @@ func HelpCopy() { password|-pw| Copy the password in an entry to your clipboard username|-u Copy the username in an entry to your clipboard totp|-t Copy the TOTP code of an entry to your clipboard - url|-l Copy the url in an entry to your clipboard + url|-l Copy the URL in an entry to your clipboard note|-n Copy the first note line in an entry to your clipboard` + "\n\n") os.Exit(0) }