code.vegaprotocol.io/vega@v0.79.0/cmd/vegawallet/commands/key_isolate.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 cmd
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  
    24  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/cli"
    25  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/flags"
    26  	"code.vegaprotocol.io/vega/cmd/vegawallet/commands/printer"
    27  	"code.vegaprotocol.io/vega/wallet/api"
    28  	"code.vegaprotocol.io/vega/wallet/wallets"
    29  
    30  	"github.com/spf13/cobra"
    31  )
    32  
    33  var (
    34  	isolateKeyLong = cli.LongDesc(`
    35  		Extract the specified key pair into an isolated wallet.
    36  
    37  		An isolated wallet is a wallet that contains a single key pair and that
    38  		has been stripped from its cryptographic node.
    39  
    40  		Removing the cryptographic node from the wallet minimizes the impact of a
    41  		stolen wallet, as it makes it impossible to retrieve or generate keys out
    42  		of it.
    43  
    44  		This creates a wallet that is only able to sign and verify transactions.
    45  
    46  		This adds an extra layer of security.
    47  	`)
    48  
    49  	isolateKeyExample = cli.Examples(`
    50  		# Isolate a key pair
    51  		{{.Software}} key isolate --wallet WALLET --pubkey PUBKEY
    52  	`)
    53  
    54  	isolatedWalletPassphraseOptions = flags.PassphraseOptions{
    55  		Name:        "isolated wallet",
    56  		Description: "When isolating the wallet, you can choose a brand-new passphrase, or reuse the original one.",
    57  	}
    58  )
    59  
    60  type IsolateKeyHandler func(api.AdminIsolateKeyParams, string) (isolateKeyResult, error)
    61  
    62  type isolateKeyResult struct {
    63  	Wallet   string `json:"wallet"`
    64  	FilePath string `json:"filePath"`
    65  }
    66  
    67  func NewCmdIsolateKey(w io.Writer, rf *RootFlags) *cobra.Command {
    68  	h := func(params api.AdminIsolateKeyParams, passphrase string) (isolateKeyResult, error) {
    69  		ctx := context.Background()
    70  
    71  		walletStore, err := wallets.InitialiseStore(rf.Home, false)
    72  		if err != nil {
    73  			return isolateKeyResult{}, fmt.Errorf("could not initialise wallets store: %w", err)
    74  		}
    75  		defer walletStore.Close()
    76  
    77  		if _, errDetails := api.NewAdminUnlockWallet(walletStore).Handle(ctx, api.AdminUnlockWalletParams{
    78  			Wallet:     params.Wallet,
    79  			Passphrase: passphrase,
    80  		}); errDetails != nil {
    81  			return isolateKeyResult{}, errors.New(errDetails.Data)
    82  		}
    83  
    84  		rawResult, errDetails := api.NewAdminIsolateKey(walletStore).Handle(ctx, params)
    85  		if errDetails != nil {
    86  			return isolateKeyResult{}, errors.New(errDetails.Data)
    87  		}
    88  		result := rawResult.(api.AdminIsolateKeyResult)
    89  		return isolateKeyResult{
    90  			Wallet:   result.Wallet,
    91  			FilePath: walletStore.GetWalletPath(result.Wallet),
    92  		}, nil
    93  	}
    94  
    95  	return BuildCmdIsolateKey(w, h, rf)
    96  }
    97  
    98  func BuildCmdIsolateKey(w io.Writer, handler IsolateKeyHandler, rf *RootFlags) *cobra.Command {
    99  	f := &IsolateKeyFlags{}
   100  
   101  	cmd := &cobra.Command{
   102  		Use:     "isolate",
   103  		Short:   "Extract the specified key pair into an isolated wallet",
   104  		Long:    isolateKeyLong,
   105  		Example: isolateKeyExample,
   106  		RunE: func(_ *cobra.Command, _ []string) error {
   107  			req, pass, err := f.Validate()
   108  			if err != nil {
   109  				return err
   110  			}
   111  
   112  			resp, err := handler(req, pass)
   113  			if err != nil {
   114  				return err
   115  			}
   116  
   117  			switch rf.Output {
   118  			case flags.InteractiveOutput:
   119  				PrintIsolateKeyResponse(w, resp)
   120  			case flags.JSONOutput:
   121  				return printer.FprintJSON(w, resp)
   122  			}
   123  
   124  			return nil
   125  		},
   126  	}
   127  
   128  	cmd.Flags().StringVarP(&f.Wallet,
   129  		"wallet", "w",
   130  		"",
   131  		"Wallet holding the public key",
   132  	)
   133  	cmd.Flags().StringVarP(&f.PubKey,
   134  		"pubkey", "k",
   135  		"",
   136  		"Public key to isolate (hex-encoded)",
   137  	)
   138  	cmd.Flags().StringVarP(&f.PassphraseFile,
   139  		"passphrase-file", "p",
   140  		"",
   141  		"Path to the file containing the wallet's passphrase",
   142  	)
   143  	cmd.Flags().StringVar(&f.IsolatedWalletPassphraseFile,
   144  		"isolated-wallet-passphrase-file",
   145  		"",
   146  		"Path to the file containing the new isolated wallet's passphrase",
   147  	)
   148  
   149  	autoCompleteWallet(cmd, rf.Home, "wallet")
   150  
   151  	return cmd
   152  }
   153  
   154  type IsolateKeyFlags struct {
   155  	Wallet                       string
   156  	PubKey                       string
   157  	PassphraseFile               string
   158  	IsolatedWalletPassphraseFile string
   159  }
   160  
   161  func (f *IsolateKeyFlags) Validate() (api.AdminIsolateKeyParams, string, error) {
   162  	if len(f.Wallet) == 0 {
   163  		return api.AdminIsolateKeyParams{}, "", flags.MustBeSpecifiedError("wallet")
   164  	}
   165  
   166  	if len(f.PubKey) == 0 {
   167  		return api.AdminIsolateKeyParams{}, "", flags.MustBeSpecifiedError("pubkey")
   168  	}
   169  
   170  	passphrase, err := flags.GetPassphrase(f.PassphraseFile)
   171  	if err != nil {
   172  		return api.AdminIsolateKeyParams{}, "", err
   173  	}
   174  
   175  	newPassphrase, err := flags.GetConfirmedPassphraseWithContext(isolatedWalletPassphraseOptions, f.IsolatedWalletPassphraseFile)
   176  	if err != nil {
   177  		return api.AdminIsolateKeyParams{}, "", err
   178  	}
   179  
   180  	return api.AdminIsolateKeyParams{
   181  		Wallet:                   f.Wallet,
   182  		PublicKey:                f.PubKey,
   183  		IsolatedWalletPassphrase: newPassphrase,
   184  	}, passphrase, nil
   185  }
   186  
   187  func PrintIsolateKeyResponse(w io.Writer, resp isolateKeyResult) {
   188  	p := printer.NewInteractivePrinter(w)
   189  
   190  	str := p.String()
   191  	defer p.Print(str)
   192  	str.CheckMark().Text("Key pair has been isolated in wallet ").Bold(resp.Wallet).Text(" at: ").SuccessText(resp.FilePath).NextLine()
   193  	str.CheckMark().SuccessText("Key isolation succeeded").NextLine()
   194  }