1package command
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"os"
8	"strings"
9
10	"github.com/fatih/structs"
11	"github.com/hashicorp/vault/api"
12	"github.com/hashicorp/vault/helper/pgpkeys"
13	"github.com/hashicorp/vault/sdk/helper/password"
14	"github.com/mitchellh/cli"
15	"github.com/posener/complete"
16)
17
18var _ cli.Command = (*OperatorRekeyCommand)(nil)
19var _ cli.CommandAutocomplete = (*OperatorRekeyCommand)(nil)
20
21type OperatorRekeyCommand struct {
22	*BaseCommand
23
24	flagCancel       bool
25	flagInit         bool
26	flagKeyShares    int
27	flagKeyThreshold int
28	flagNonce        string
29	flagPGPKeys      []string
30	flagStatus       bool
31	flagTarget       string
32	flagVerify       bool
33
34	// Backup options
35	flagBackup         bool
36	flagBackupDelete   bool
37	flagBackupRetrieve bool
38
39	testStdin io.Reader // for tests
40}
41
42func (c *OperatorRekeyCommand) Synopsis() string {
43	return "Generates new unseal keys"
44}
45
46func (c *OperatorRekeyCommand) Help() string {
47	helpText := `
48Usage: vault operator rekey [options] [KEY]
49
50  Generates a new set of unseal keys. This can optionally change the total
51  number of key shares or the required threshold of those key shares to
52  reconstruct the master key. This operation is zero downtime, but it requires
53  the Vault is unsealed and a quorum of existing unseal keys are provided.
54
55  An unseal key may be provided directly on the command line as an argument to
56  the command. If key is specified as "-", the command will read from stdin. If
57  a TTY is available, the command will prompt for text.
58
59  Initialize a rekey:
60
61      $ vault operator rekey \
62          -init \
63          -key-shares=15 \
64          -key-threshold=9
65
66  Rekey and encrypt the resulting unseal keys with PGP:
67
68      $ vault operator rekey \
69          -init \
70          -key-shares=3 \
71          -key-threshold=2 \
72          -pgp-keys="keybase:hashicorp,keybase:jefferai,keybase:sethvargo"
73
74  Store encrypted PGP keys in Vault's core:
75
76      $ vault operator rekey \
77          -init \
78          -pgp-keys="..." \
79          -backup
80
81  Retrieve backed-up unseal keys:
82
83      $ vault operator rekey -backup-retrieve
84
85  Delete backed-up unseal keys:
86
87      $ vault operator rekey -backup-delete
88
89` + c.Flags().Help()
90	return strings.TrimSpace(helpText)
91}
92
93func (c *OperatorRekeyCommand) Flags() *FlagSets {
94	set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
95
96	f := set.NewFlagSet("Common Options")
97
98	f.BoolVar(&BoolVar{
99		Name:    "init",
100		Target:  &c.flagInit,
101		Default: false,
102		Usage: "Initialize the rekeying operation. This can only be done if no " +
103			"rekeying operation is in progress. Customize the new number of key " +
104			"shares and key threshold using the -key-shares and -key-threshold " +
105			"flags.",
106	})
107
108	f.BoolVar(&BoolVar{
109		Name:    "cancel",
110		Target:  &c.flagCancel,
111		Default: false,
112		Usage: "Reset the rekeying progress. This will discard any submitted " +
113			"unseal keys or configuration.",
114	})
115
116	f.BoolVar(&BoolVar{
117		Name:    "status",
118		Target:  &c.flagStatus,
119		Default: false,
120		Usage: "Print the status of the current attempt without providing an " +
121			"unseal key.",
122	})
123
124	f.IntVar(&IntVar{
125		Name:       "key-shares",
126		Aliases:    []string{"n"},
127		Target:     &c.flagKeyShares,
128		Default:    5,
129		Completion: complete.PredictAnything,
130		Usage: "Number of key shares to split the generated master key into. " +
131			"This is the number of \"unseal keys\" to generate.",
132	})
133
134	f.IntVar(&IntVar{
135		Name:       "key-threshold",
136		Aliases:    []string{"t"},
137		Target:     &c.flagKeyThreshold,
138		Default:    3,
139		Completion: complete.PredictAnything,
140		Usage: "Number of key shares required to reconstruct the master key. " +
141			"This must be less than or equal to -key-shares.",
142	})
143
144	f.StringVar(&StringVar{
145		Name:       "nonce",
146		Target:     &c.flagNonce,
147		Default:    "",
148		EnvVar:     "",
149		Completion: complete.PredictAnything,
150		Usage: "Nonce value provided at initialization. The same nonce value " +
151			"must be provided with each unseal key.",
152	})
153
154	f.StringVar(&StringVar{
155		Name:       "target",
156		Target:     &c.flagTarget,
157		Default:    "barrier",
158		EnvVar:     "",
159		Completion: complete.PredictSet("barrier", "recovery"),
160		Usage: "Target for rekeying. \"recovery\" only applies when HSM support " +
161			"is enabled.",
162	})
163
164	f.BoolVar(&BoolVar{
165		Name:    "verify",
166		Target:  &c.flagVerify,
167		Default: false,
168		Usage: "Indicates that the action (-status, -cancel, or providing a key " +
169			"share) will be affecting verification for the current rekey " +
170			"attempt.",
171	})
172
173	f.VarFlag(&VarFlag{
174		Name:       "pgp-keys",
175		Value:      (*pgpkeys.PubKeyFilesFlag)(&c.flagPGPKeys),
176		Completion: complete.PredictAnything,
177		Usage: "Comma-separated list of paths to files on disk containing " +
178			"public GPG keys OR a comma-separated list of Keybase usernames using " +
179			"the format \"keybase:<username>\". When supplied, the generated " +
180			"unseal keys will be encrypted and base64-encoded in the order " +
181			"specified in this list.",
182	})
183
184	f = set.NewFlagSet("Backup Options")
185
186	f.BoolVar(&BoolVar{
187		Name:    "backup",
188		Target:  &c.flagBackup,
189		Default: false,
190		Usage: "Store a backup of the current PGP encrypted unseal keys in " +
191			"Vault's core. The encrypted values can be recovered in the event of " +
192			"failure or discarded after success. See the -backup-delete and " +
193			"-backup-retrieve options for more information. This option only " +
194			"applies when the existing unseal keys were PGP encrypted.",
195	})
196
197	f.BoolVar(&BoolVar{
198		Name:    "backup-delete",
199		Target:  &c.flagBackupDelete,
200		Default: false,
201		Usage:   "Delete any stored backup unseal keys.",
202	})
203
204	f.BoolVar(&BoolVar{
205		Name:    "backup-retrieve",
206		Target:  &c.flagBackupRetrieve,
207		Default: false,
208		Usage: "Retrieve the backed-up unseal keys. This option is only available " +
209			"if the PGP keys were provided and the backup has not been deleted.",
210	})
211
212	return set
213}
214
215func (c *OperatorRekeyCommand) AutocompleteArgs() complete.Predictor {
216	return complete.PredictAnything
217}
218
219func (c *OperatorRekeyCommand) AutocompleteFlags() complete.Flags {
220	return c.Flags().Completions()
221}
222
223func (c *OperatorRekeyCommand) Run(args []string) int {
224	f := c.Flags()
225
226	if err := f.Parse(args); err != nil {
227		c.UI.Error(err.Error())
228		return 1
229	}
230
231	args = f.Args()
232	if len(args) > 1 {
233		c.UI.Error(fmt.Sprintf("Too many arguments (expected 0-1, got %d)", len(args)))
234		return 1
235	}
236
237	// Create the client
238	client, err := c.Client()
239	if err != nil {
240		c.UI.Error(err.Error())
241		return 2
242	}
243
244	switch {
245	case c.flagBackupDelete:
246		return c.backupDelete(client)
247	case c.flagBackupRetrieve:
248		return c.backupRetrieve(client)
249	case c.flagCancel:
250		return c.cancel(client)
251	case c.flagInit:
252		return c.init(client)
253	case c.flagStatus:
254		return c.status(client)
255	default:
256		// If there are no other flags, prompt for an unseal key.
257		key := ""
258		if len(args) > 0 {
259			key = strings.TrimSpace(args[0])
260		}
261		return c.provide(client, key)
262	}
263}
264
265// init starts the rekey process.
266func (c *OperatorRekeyCommand) init(client *api.Client) int {
267	// Handle the different API requests
268	var fn func(*api.RekeyInitRequest) (*api.RekeyStatusResponse, error)
269	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
270	case "barrier":
271		fn = client.Sys().RekeyInit
272	case "recovery", "hsm":
273		fn = client.Sys().RekeyRecoveryKeyInit
274	default:
275		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
276		return 1
277	}
278
279	// Make the request
280	status, err := fn(&api.RekeyInitRequest{
281		SecretShares:        c.flagKeyShares,
282		SecretThreshold:     c.flagKeyThreshold,
283		PGPKeys:             c.flagPGPKeys,
284		Backup:              c.flagBackup,
285		RequireVerification: c.flagVerify,
286	})
287	if err != nil {
288		c.UI.Error(fmt.Sprintf("Error initializing rekey: %s", err))
289		return 2
290	}
291
292	// Print warnings about recovery, etc.
293	if len(c.flagPGPKeys) == 0 {
294		if Format(c.UI) == "table" {
295			c.UI.Warn(wrapAtLength(
296				"WARNING! If you lose the keys after they are returned, there is no " +
297					"recovery. Consider canceling this operation and re-initializing " +
298					"with the -pgp-keys flag to protect the returned unseal keys along " +
299					"with -backup to allow recovery of the encrypted keys in case of " +
300					"emergency. You can delete the stored keys later using the -delete " +
301					"flag."))
302			c.UI.Output("")
303		}
304	}
305	if len(c.flagPGPKeys) > 0 && !c.flagBackup {
306		if Format(c.UI) == "table" {
307			c.UI.Warn(wrapAtLength(
308				"WARNING! You are using PGP keys for encrypted the resulting unseal " +
309					"keys, but you did not enable the option to backup the keys to " +
310					"Vault's core. If you lose the encrypted keys after they are " +
311					"returned, you will not be able to recover them. Consider canceling " +
312					"this operation and re-running with -backup to allow recovery of the " +
313					"encrypted unseal keys in case of emergency. You can delete the " +
314					"stored keys later using the -delete flag."))
315			c.UI.Output("")
316		}
317	}
318
319	// Provide the current status
320	return c.printStatus(status)
321}
322
323// cancel is used to abort the rekey process.
324func (c *OperatorRekeyCommand) cancel(client *api.Client) int {
325	// Handle the different API requests
326	var fn func() error
327	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
328	case "barrier":
329		fn = client.Sys().RekeyCancel
330		if c.flagVerify {
331			fn = client.Sys().RekeyVerificationCancel
332		}
333	case "recovery", "hsm":
334		fn = client.Sys().RekeyRecoveryKeyCancel
335		if c.flagVerify {
336			fn = client.Sys().RekeyRecoveryKeyVerificationCancel
337		}
338
339	default:
340		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
341		return 1
342	}
343
344	// Make the request
345	if err := fn(); err != nil {
346		c.UI.Error(fmt.Sprintf("Error canceling rekey: %s", err))
347		return 2
348	}
349
350	c.UI.Output("Success! Canceled rekeying (if it was started)")
351	return 0
352}
353
354// provide prompts the user for the seal key and posts it to the update root
355// endpoint. If this is the last unseal, this function outputs it.
356func (c *OperatorRekeyCommand) provide(client *api.Client, key string) int {
357	var statusFn func() (interface{}, error)
358	var updateFn func(string, string) (interface{}, error)
359
360	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
361	case "barrier":
362		statusFn = func() (interface{}, error) {
363			return client.Sys().RekeyStatus()
364		}
365		updateFn = func(s1 string, s2 string) (interface{}, error) {
366			return client.Sys().RekeyUpdate(s1, s2)
367		}
368		if c.flagVerify {
369			statusFn = func() (interface{}, error) {
370				return client.Sys().RekeyVerificationStatus()
371			}
372			updateFn = func(s1 string, s2 string) (interface{}, error) {
373				return client.Sys().RekeyVerificationUpdate(s1, s2)
374			}
375		}
376	case "recovery", "hsm":
377		statusFn = func() (interface{}, error) {
378			return client.Sys().RekeyRecoveryKeyStatus()
379		}
380		updateFn = func(s1 string, s2 string) (interface{}, error) {
381			return client.Sys().RekeyRecoveryKeyUpdate(s1, s2)
382		}
383		if c.flagVerify {
384			statusFn = func() (interface{}, error) {
385				return client.Sys().RekeyRecoveryKeyVerificationStatus()
386			}
387			updateFn = func(s1 string, s2 string) (interface{}, error) {
388				return client.Sys().RekeyRecoveryKeyVerificationUpdate(s1, s2)
389			}
390		}
391	default:
392		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
393		return 1
394	}
395
396	status, err := statusFn()
397	if err != nil {
398		c.UI.Error(fmt.Sprintf("Error getting rekey status: %s", err))
399		return 2
400	}
401
402	var started bool
403	var nonce string
404
405	switch status.(type) {
406	case *api.RekeyStatusResponse:
407		stat := status.(*api.RekeyStatusResponse)
408		started = stat.Started
409		nonce = stat.Nonce
410	case *api.RekeyVerificationStatusResponse:
411		stat := status.(*api.RekeyVerificationStatusResponse)
412		started = stat.Started
413		nonce = stat.Nonce
414	default:
415		c.UI.Error("Unknown status type")
416		return 1
417	}
418
419	// Verify a root token generation is in progress. If there is not one in
420	// progress, return an error instructing the user to start one.
421	if !started {
422		c.UI.Error(wrapAtLength(
423			"No rekey is in progress. Start a rekey process by running " +
424				"\"vault operator rekey -init\"."))
425		return 1
426	}
427
428	switch key {
429	case "-": // Read from stdin
430		nonce = c.flagNonce
431
432		// Pull our fake stdin if needed
433		stdin := (io.Reader)(os.Stdin)
434		if c.testStdin != nil {
435			stdin = c.testStdin
436		}
437
438		var buf bytes.Buffer
439		if _, err := io.Copy(&buf, stdin); err != nil {
440			c.UI.Error(fmt.Sprintf("Failed to read from stdin: %s", err))
441			return 1
442		}
443
444		key = buf.String()
445	case "": // Prompt using the tty
446		// Nonce value is not required if we are prompting via the terminal
447		w := getWriterFromUI(c.UI)
448		fmt.Fprintf(w, "Rekey operation nonce: %s\n", nonce)
449		fmt.Fprintf(w, "Unseal Key (will be hidden): ")
450		key, err = password.Read(os.Stdin)
451		fmt.Fprintf(w, "\n")
452		if err != nil {
453			if err == password.ErrInterrupted {
454				c.UI.Error("user canceled")
455				return 1
456			}
457
458			c.UI.Error(wrapAtLength(fmt.Sprintf("An error occurred attempting to "+
459				"ask for the unseal key. The raw error message is shown below, but "+
460				"usually this is because you attempted to pipe a value into the "+
461				"command or you are executing outside of a terminal (tty). If you "+
462				"want to pipe the value, pass \"-\" as the argument to read from "+
463				"stdin. The raw error was: %s", err)))
464			return 1
465		}
466	default: // Supplied directly as an arg
467		nonce = c.flagNonce
468	}
469
470	// Trim any whitespace from they key, especially since we might have
471	// prompted the user for it.
472	key = strings.TrimSpace(key)
473
474	// Verify we have a nonce value
475	if nonce == "" {
476		c.UI.Error("Missing nonce value: specify it via the -nonce flag")
477		return 1
478	}
479
480	// Provide the key, this may potentially complete the update
481	resp, err := updateFn(key, nonce)
482	if err != nil {
483		c.UI.Error(fmt.Sprintf("Error posting unseal key: %s", err))
484		return 2
485	}
486
487	var complete bool
488	var mightContainUnsealKeys bool
489
490	switch resp.(type) {
491	case *api.RekeyUpdateResponse:
492		complete = resp.(*api.RekeyUpdateResponse).Complete
493		mightContainUnsealKeys = true
494	case *api.RekeyVerificationUpdateResponse:
495		complete = resp.(*api.RekeyVerificationUpdateResponse).Complete
496	default:
497		c.UI.Error("Unknown update response type")
498		return 1
499	}
500
501	if !complete {
502		return c.status(client)
503	}
504
505	if mightContainUnsealKeys {
506		return c.printUnsealKeys(client, status.(*api.RekeyStatusResponse),
507			resp.(*api.RekeyUpdateResponse))
508	}
509
510	c.UI.Output(wrapAtLength("Rekey verification successful. The rekey operation is complete and the new keys are now active."))
511	return 0
512}
513
514// status is used just to fetch and dump the status.
515func (c *OperatorRekeyCommand) status(client *api.Client) int {
516	// Handle the different API requests
517	var fn func() (interface{}, error)
518	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
519	case "barrier":
520		fn = func() (interface{}, error) {
521			return client.Sys().RekeyStatus()
522		}
523		if c.flagVerify {
524			fn = func() (interface{}, error) {
525				return client.Sys().RekeyVerificationStatus()
526			}
527		}
528	case "recovery", "hsm":
529		fn = func() (interface{}, error) {
530			return client.Sys().RekeyRecoveryKeyStatus()
531		}
532		if c.flagVerify {
533			fn = func() (interface{}, error) {
534				return client.Sys().RekeyRecoveryKeyVerificationStatus()
535			}
536		}
537	default:
538		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
539		return 1
540	}
541
542	// Make the request
543	status, err := fn()
544	if err != nil {
545		c.UI.Error(fmt.Sprintf("Error reading rekey status: %s", err))
546		return 2
547	}
548
549	return c.printStatus(status)
550}
551
552// backupRetrieve retrieves the stored backup keys.
553func (c *OperatorRekeyCommand) backupRetrieve(client *api.Client) int {
554	// Handle the different API requests
555	var fn func() (*api.RekeyRetrieveResponse, error)
556	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
557	case "barrier":
558		fn = client.Sys().RekeyRetrieveBackup
559	case "recovery", "hsm":
560		fn = client.Sys().RekeyRetrieveRecoveryBackup
561	default:
562		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
563		return 1
564	}
565
566	// Make the request
567	storedKeys, err := fn()
568	if err != nil {
569		c.UI.Error(fmt.Sprintf("Error retrieving rekey stored keys: %s", err))
570		return 2
571	}
572
573	secret := &api.Secret{
574		Data: structs.New(storedKeys).Map(),
575	}
576
577	return OutputSecret(c.UI, secret)
578}
579
580// backupDelete deletes the stored backup keys.
581func (c *OperatorRekeyCommand) backupDelete(client *api.Client) int {
582	// Handle the different API requests
583	var fn func() error
584	switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
585	case "barrier":
586		fn = client.Sys().RekeyDeleteBackup
587	case "recovery", "hsm":
588		fn = client.Sys().RekeyDeleteRecoveryBackup
589	default:
590		c.UI.Error(fmt.Sprintf("Unknown target: %s", c.flagTarget))
591		return 1
592	}
593
594	// Make the request
595	if err := fn(); err != nil {
596		c.UI.Error(fmt.Sprintf("Error deleting rekey stored keys: %s", err))
597		return 2
598	}
599
600	c.UI.Output("Success! Delete stored keys (if they existed)")
601	return 0
602}
603
604// printStatus dumps the status to output
605func (c *OperatorRekeyCommand) printStatus(in interface{}) int {
606	out := []string{}
607	out = append(out, "Key | Value")
608
609	switch in.(type) {
610	case *api.RekeyStatusResponse:
611		status := in.(*api.RekeyStatusResponse)
612		out = append(out, fmt.Sprintf("Nonce | %s", status.Nonce))
613		out = append(out, fmt.Sprintf("Started | %t", status.Started))
614		if status.Started {
615			if status.Progress == status.Required {
616				out = append(out, fmt.Sprintf("Rekey Progress | %d/%d (verification in progress)", status.Progress, status.Required))
617			} else {
618				out = append(out, fmt.Sprintf("Rekey Progress | %d/%d", status.Progress, status.Required))
619			}
620			out = append(out, fmt.Sprintf("New Shares | %d", status.N))
621			out = append(out, fmt.Sprintf("New Threshold | %d", status.T))
622			out = append(out, fmt.Sprintf("Verification Required | %t", status.VerificationRequired))
623			if status.VerificationNonce != "" {
624				out = append(out, fmt.Sprintf("Verification Nonce | %s", status.VerificationNonce))
625			}
626		}
627		if len(status.PGPFingerprints) > 0 {
628			out = append(out, fmt.Sprintf("PGP Fingerprints | %s", status.PGPFingerprints))
629			out = append(out, fmt.Sprintf("Backup | %t", status.Backup))
630		}
631	case *api.RekeyVerificationStatusResponse:
632		status := in.(*api.RekeyVerificationStatusResponse)
633		out = append(out, fmt.Sprintf("Started | %t", status.Started))
634		out = append(out, fmt.Sprintf("New Shares | %d", status.N))
635		out = append(out, fmt.Sprintf("New Threshold | %d", status.T))
636		out = append(out, fmt.Sprintf("Verification Nonce | %s", status.Nonce))
637		out = append(out, fmt.Sprintf("Verification Progress | %d/%d", status.Progress, status.T))
638	default:
639		c.UI.Error("Unknown status type")
640		return 1
641	}
642
643	switch Format(c.UI) {
644	case "table":
645		c.UI.Output(tableOutput(out, nil))
646		return 0
647	default:
648		return OutputData(c.UI, in)
649	}
650}
651
652func (c *OperatorRekeyCommand) printUnsealKeys(client *api.Client, status *api.RekeyStatusResponse, resp *api.RekeyUpdateResponse) int {
653	switch Format(c.UI) {
654	case "table":
655	default:
656		return OutputData(c.UI, resp)
657	}
658
659	// Space between the key prompt, if any, and the output
660	c.UI.Output("")
661
662	// Provide the keys
663	var haveB64 bool
664	if resp.KeysB64 != nil && len(resp.KeysB64) == len(resp.Keys) {
665		haveB64 = true
666	}
667	for i, key := range resp.Keys {
668		if len(resp.PGPFingerprints) > 0 {
669			if haveB64 {
670				c.UI.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, resp.PGPFingerprints[i], resp.KeysB64[i]))
671			} else {
672				c.UI.Output(fmt.Sprintf("Key %d fingerprint: %s; value: %s", i+1, resp.PGPFingerprints[i], key))
673			}
674		} else {
675			if haveB64 {
676				c.UI.Output(fmt.Sprintf("Key %d: %s", i+1, resp.KeysB64[i]))
677			} else {
678				c.UI.Output(fmt.Sprintf("Key %d: %s", i+1, key))
679			}
680		}
681	}
682
683	c.UI.Output("")
684	c.UI.Output(fmt.Sprintf("Operation nonce: %s", resp.Nonce))
685
686	if len(resp.PGPFingerprints) > 0 && resp.Backup {
687		c.UI.Output("")
688		switch strings.ToLower(strings.TrimSpace(c.flagTarget)) {
689		case "barrier":
690			c.UI.Output(wrapAtLength(fmt.Sprintf(
691				"The encrypted unseal keys are backed up to \"core/unseal-keys-backup\" " +
692					"in the storage backend. Remove these keys at any time using " +
693					"\"vault operator rekey -backup-delete\". Vault does not automatically " +
694					"remove these keys.",
695			)))
696		case "recovery", "hsm":
697			c.UI.Output(wrapAtLength(fmt.Sprintf(
698				"The encrypted unseal keys are backed up to \"core/recovery-keys-backup\" " +
699					"in the storage backend. Remove these keys at any time using " +
700					"\"vault operator rekey -backup-delete -target=recovery\". Vault does not automatically " +
701					"remove these keys.",
702			)))
703		}
704	}
705
706	switch status.VerificationRequired {
707	case false:
708		c.UI.Output("")
709		c.UI.Output(wrapAtLength(fmt.Sprintf(
710			"Vault rekeyed with %d key shares and a key threshold of %d. Please "+
711				"securely distribute the key shares printed above. When Vault is "+
712				"re-sealed, restarted, or stopped, you must supply at least %d of "+
713				"these keys to unseal it before it can start servicing requests.",
714			status.N,
715			status.T,
716			status.T)))
717	default:
718		c.UI.Output("")
719		c.UI.Output(wrapAtLength(fmt.Sprintf(
720			"Vault has created a new key, split into %d key shares and a key threshold "+
721				"of %d. These will not be active until after verification is complete. "+
722				"Please securely distribute the key shares printed above. When Vault "+
723				"is re-sealed, restarted, or stopped, you must supply at least %d of "+
724				"these keys to unseal it before it can start servicing requests.",
725			status.N,
726			status.T,
727			status.T)))
728		c.UI.Output("")
729		c.UI.Warn(wrapAtLength(
730			"Again, these key shares are _not_ valid until verification is performed. " +
731				"Do not lose or discard your current key shares until after verification " +
732				"is complete or you will be unable to unseal Vault. If you cancel the " +
733				"rekey process or seal Vault before verification is complete the new " +
734				"shares will be discarded and the current shares will remain valid.",
735		))
736		c.UI.Output("")
737		c.UI.Warn(wrapAtLength(
738			"The current verification status, including initial nonce, is shown below.",
739		))
740		c.UI.Output("")
741
742		c.flagVerify = true
743		return c.status(client)
744	}
745
746	return 0
747}
748