code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/flags/passphrase.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package flags 17 18 import ( 19 "errors" 20 "fmt" 21 "os" 22 "strings" 23 24 vgfs "code.vegaprotocol.io/vega/libs/fs" 25 vgterm "code.vegaprotocol.io/vega/libs/term" 26 27 "golang.org/x/term" 28 ) 29 30 var ( 31 ErrPassphraseRequiredWithoutTTY = errors.New("passphrase flag is required without TTY") 32 ErrPassphraseDoNotMatch = errors.New("passphrases do not match") 33 ErrPassphraseMustBeSpecified = errors.New("passphrase must be specified") 34 ErrMsysPasswordInput = errors.New("password input is not supported on msys (use --passphrase-file or a standard windows terminal)") 35 ) 36 37 type PassphraseOptions struct { 38 Name string 39 Description string 40 } 41 42 type PassphraseGetterWithOps func(PassphraseOptions, bool) (string, error) 43 44 // BuildPassphraseGetterWithOps builds a function that returns a passphrase. 45 // If passphraseFile is set, the returned function is built to read a file. If 46 // it's not set, the returned function is built to read from user input. 47 // The one based on the user input takes an argument withConfirmation that 48 // asks for passphrase confirmation base on its value. 49 func BuildPassphraseGetterWithOps(passphraseFile string) PassphraseGetterWithOps { 50 if len(passphraseFile) != 0 { 51 return func(_ PassphraseOptions, _ bool) (string, error) { 52 return ReadPassphraseFile(passphraseFile) 53 } 54 } 55 56 return ReadPassphraseInputWithOpts 57 } 58 59 func GetPassphrase(passphraseFile string) (string, error) { 60 if len(passphraseFile) != 0 { 61 return ReadPassphraseFile(passphraseFile) 62 } 63 64 return ReadPassphraseInput(PassphraseOptions{}) 65 } 66 67 func GetPassphraseWithOptions(options PassphraseOptions, passphraseFile string) (string, error) { 68 if len(passphraseFile) != 0 { 69 return ReadPassphraseFile(passphraseFile) 70 } 71 72 return ReadPassphraseInput(options) 73 } 74 75 func GetConfirmedPassphrase(passphraseFile string) (string, error) { 76 if len(passphraseFile) != 0 { 77 return ReadPassphraseFile(passphraseFile) 78 } 79 80 return ReadConfirmedPassphraseInput(PassphraseOptions{}) 81 } 82 83 func GetConfirmedPassphraseWithContext(passphraseOptions PassphraseOptions, passphraseFile string) (string, error) { 84 if len(passphraseFile) != 0 { 85 return ReadPassphraseFile(passphraseFile) 86 } 87 88 return ReadConfirmedPassphraseInput(passphraseOptions) 89 } 90 91 func ReadPassphraseFile(passphraseFilePath string) (string, error) { 92 rawPassphrase, err := vgfs.ReadFile(passphraseFilePath) 93 if err != nil { 94 return "", fmt.Errorf("couldn't read passphrase file: %w", err) 95 } 96 97 // user might have added a newline at the end of the line, let's remove it, 98 // remembering Windows does things differently 99 cleanupPassphrase := strings.Trim(string(rawPassphrase), "\r\n") 100 if len(cleanupPassphrase) == 0 { 101 return "", ErrPassphraseMustBeSpecified 102 } 103 104 return cleanupPassphrase, nil 105 } 106 107 func ReadPassphraseInput(options PassphraseOptions) (string, error) { 108 return ReadPassphraseInputWithOpts(options, false) 109 } 110 111 func ReadConfirmedPassphraseInput(passphraseContext PassphraseOptions) (string, error) { 112 return ReadPassphraseInputWithOpts(passphraseContext, true) 113 } 114 115 func ReadPassphraseInputWithOpts(passphraseOptions PassphraseOptions, withConfirmation bool) (string, error) { 116 if vgterm.HasNoTTY() { 117 return "", ErrPassphraseRequiredWithoutTTY 118 } 119 120 if passphraseOptions.Description != "" { 121 fmt.Printf("\n" + passphraseOptions.Description + "\n") 122 } 123 if passphraseOptions.Name != "" { 124 passphraseOptions.Name += " " 125 } 126 passphrase, err := promptForPassphrase("Enter " + passphraseOptions.Name + "passphrase: ") 127 if err != nil { 128 return "", fmt.Errorf("couldn't get passphrase: %w", err) 129 } 130 if len(passphrase) == 0 { 131 return "", ErrPassphraseMustBeSpecified 132 } 133 134 if withConfirmation { 135 confirmation, err := promptForPassphrase("Confirm passphrase: ") 136 if err != nil { 137 return "", fmt.Errorf("couldn't get passphrase confirmation: %w", err) 138 } 139 140 if passphrase != confirmation { 141 return "", ErrPassphraseDoNotMatch 142 } 143 } 144 fmt.Println() //nolint:forbidigo 145 146 return passphrase, nil 147 } 148 149 func runningInMsys() bool { 150 ms := os.Getenv("MSYSTEM") 151 return ms != "" 152 } 153 154 func promptForPassphrase(msg ...string) (string, error) { 155 if runningInMsys() { 156 return "", ErrMsysPasswordInput 157 } 158 fmt.Print(msg[0]) //nolint:forbidigo 159 password, err := term.ReadPassword(int(os.Stdin.Fd())) 160 if err != nil { 161 return "", fmt.Errorf("couldn't read password input: %w", err) 162 } 163 fmt.Println() //nolint:forbidigo 164 165 return string(password), nil 166 }