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  }