1package action 2 3import ( 4 "context" 5 "fmt" 6 7 "github.com/gopasspw/gopass/internal/tree" 8 9 "github.com/gopasspw/gopass/internal/cui" 10 "github.com/gopasspw/gopass/internal/out" 11 "github.com/gopasspw/gopass/pkg/ctxutil" 12 "github.com/gopasspw/gopass/pkg/debug" 13 "github.com/gopasspw/gopass/pkg/termio" 14 15 "github.com/urfave/cli/v2" 16) 17 18var ( 19 removalWarning = ` 20 21 22@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 23@ WARNING: REMOVING A USER WILL NOT REVOKE ACCESS FROM OLD REVISONS! @ 24@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 25THE USER %s WILL STILL BE ABLE TO ACCESS ANY OLD COPY OF THE STORE AND 26ANY OLD REVISION THEY HAD ACCESS TO. 27 28ANY CREDENTIALS THIS USER HAD ACCESS TO NEED TO BE CONSIDERED COMPROMISED 29AND SHOULD BE REVOKED. 30 31This feature is only meant for revoking access to any added or changed 32credentials. 33 34` 35) 36 37// RecipientsPrint prints all recipients per store 38func (s *Action) RecipientsPrint(c *cli.Context) error { 39 ctx := ctxutil.WithGlobalFlags(c) 40 out.Printf(ctx, "Hint: run 'gopass sync' to import any missing public keys") 41 42 t, err := s.Store.RecipientsTree(ctx, true) 43 if err != nil { 44 return ExitError(ExitList, err, "failed to list recipients: %s", err) 45 } 46 47 fmt.Fprintln(stdout, t.Format(tree.INF)) 48 return nil 49} 50 51func (s *Action) recipientsList(ctx context.Context) []string { 52 t, err := s.Store.RecipientsTree(ctxutil.WithHidden(ctx, true), false) 53 if err != nil { 54 debug.Log("failed to list recipients: %s", err) 55 return nil 56 } 57 58 return t.List(tree.INF) 59} 60 61// RecipientsComplete will print a list of recipients for bash 62// completion 63func (s *Action) RecipientsComplete(c *cli.Context) { 64 ctx := ctxutil.WithGlobalFlags(c) 65 for _, v := range s.recipientsList(ctx) { 66 fmt.Fprintln(stdout, v) 67 } 68} 69 70// RecipientsAdd adds new recipients 71func (s *Action) RecipientsAdd(c *cli.Context) error { 72 ctx := ctxutil.WithGlobalFlags(c) 73 store := c.String("store") 74 force := c.Bool("force") 75 added := 0 76 77 // select store 78 if store == "" { 79 store = cui.AskForStore(ctx, s.Store) 80 } 81 82 crypto := s.Store.Crypto(ctx, store) 83 84 // select recipient 85 recipients := []string(c.Args().Slice()) 86 if len(recipients) < 1 { 87 debug.Log("no recipients given, asking for selection") 88 r, err := s.recipientsSelectForAdd(ctx, store) 89 if err != nil { 90 return err 91 } 92 recipients = r 93 } 94 95 debug.Log("adding recipients: %+v", recipients) 96 for _, r := range recipients { 97 keys, err := crypto.FindRecipients(ctx, r) 98 if err != nil { 99 out.Printf(ctx, "WARNING: Failed to list public key %q: %s", r, err) 100 if !force { 101 continue 102 } 103 keys = []string{r} 104 } 105 if len(keys) < 1 && !force && crypto.Name() == "gpgcli" { 106 out.Printf(ctx, "Warning: No matching valid key found. If the key is in your keyring you may need to validate it.") 107 out.Printf(ctx, "If this is your key: gpg --edit-key %s; trust (set to ultimate); quit", r) 108 out.Printf(ctx, "If this is not your key: gpg --edit-key %s; lsign; trust; save; quit", r) 109 out.Printf(ctx, "You may need to run 'gpg --update-trustdb' afterwards") 110 continue 111 } 112 113 recp := r 114 debug.Log("found recipients for %q: %+v", r, keys) 115 116 if !termio.AskForConfirmation(ctx, fmt.Sprintf("Do you want to add %q (key %q) as a recipient to the store %q?", crypto.FormatKey(ctx, recp, ""), recp, store)) { 117 continue 118 } 119 120 if err := s.Store.AddRecipient(ctx, store, recp); err != nil { 121 return ExitError(ExitRecipients, err, "failed to add recipient %q: %s", r, err) 122 } 123 added++ 124 } 125 if added < 1 { 126 return ExitError(ExitUnknown, nil, "no key added") 127 } 128 129 out.Printf(ctx, "\nAdded %d recipients", added) 130 out.Printf(ctx, "You need to run 'gopass sync' to push these changes") 131 return nil 132} 133 134// RecipientsRemove removes recipients 135func (s *Action) RecipientsRemove(c *cli.Context) error { 136 ctx := ctxutil.WithGlobalFlags(c) 137 store := c.String("store") 138 force := c.Bool("force") 139 removed := 0 140 141 // select store 142 if store == "" { 143 store = cui.AskForStore(ctx, s.Store) 144 } 145 146 crypto := s.Store.Crypto(ctx, store) 147 148 // select recipient 149 recipients := []string(c.Args().Slice()) 150 if len(recipients) < 1 { 151 rs, err := s.recipientsSelectForRemoval(ctx, store) 152 if err != nil { 153 return err 154 } 155 recipients = rs 156 } 157 158 for _, r := range recipients { 159 kl, err := crypto.FindIdentities(ctx, r) 160 if err == nil { 161 if len(kl) > 0 { 162 if !termio.AskForConfirmation(ctx, fmt.Sprintf("Do you want to remove yourself (%s) from the recipients?", r)) { 163 continue 164 } 165 } 166 } 167 168 keys, err := crypto.FindRecipients(ctx, r) 169 if err != nil { 170 out.Printf(ctx, "WARNING: Failed to list public key %q: %s", r, err) 171 out.Printf(ctx, "Hint: You can use `--force` to remove unknown keys.") 172 if !force { 173 continue 174 } 175 keys = []string{r} 176 } 177 if len(keys) < 1 && !force { 178 out.Printf(ctx, "Warning: No matching valid key found. If the key is in your keyring you may need to validate it.") 179 out.Printf(ctx, "If this is your key: gpg --edit-key %s; trust (set to ultimate); quit", r) 180 out.Printf(ctx, "If this is not your key: gpg --edit-key %s; lsign; trust; save; quit", r) 181 out.Printf(ctx, "You may need to run 'gpg --update-trustdb' afterwards") 182 continue 183 } 184 185 recp := r 186 if len(keys) > 0 { 187 recp = crypto.Fingerprint(ctx, keys[0]) 188 } 189 190 if err := s.Store.RemoveRecipient(ctx, store, recp); err != nil { 191 return ExitError(ExitRecipients, err, "failed to remove recipient %q: %s", recp, err) 192 } 193 fmt.Fprintf(stdout, removalWarning, r) 194 removed++ 195 } 196 if removed < 1 { 197 return ExitError(ExitUnknown, nil, "no key removed") 198 } 199 200 out.Printf(ctx, "\nRemoved %d recipients", removed) 201 out.Printf(ctx, "You need to run 'gopass sync' to push these changes") 202 return nil 203} 204 205func (s *Action) recipientsSelectForRemoval(ctx context.Context, store string) ([]string, error) { 206 crypto := s.Store.Crypto(ctx, store) 207 208 ids := s.Store.ListRecipients(ctx, store) 209 choices := make([]string, 0, len(ids)) 210 for _, id := range ids { 211 choices = append(choices, crypto.FormatKey(ctx, id, "")) 212 } 213 if len(choices) < 1 { 214 return nil, nil 215 } 216 217 act, sel := cui.GetSelection(ctx, "Remove recipient -", choices) 218 switch act { 219 case "default": 220 fallthrough 221 case "show": 222 return []string{ids[sel]}, nil 223 default: 224 return nil, ExitError(ExitAborted, nil, "user aborted") 225 } 226} 227 228func (s *Action) recipientsSelectForAdd(ctx context.Context, store string) ([]string, error) { 229 crypto := s.Store.Crypto(ctx, store) 230 231 choices := []string{} 232 kl, _ := crypto.FindRecipients(ctx) 233 for _, key := range kl { 234 choices = append(choices, crypto.FormatKey(ctx, key, "")) 235 } 236 if len(choices) < 1 { 237 return nil, nil 238 } 239 240 act, sel := cui.GetSelection(ctx, "Add Recipient -", choices) 241 switch act { 242 case "default": 243 fallthrough 244 case "show": 245 return []string{kl[sel]}, nil 246 default: 247 return nil, ExitError(ExitAborted, nil, "user aborted") 248 } 249} 250