diff --git a/commands/compose/encrypt.go b/commands/compose/encrypt.go new file mode 100644 index 0000000..d63940b --- /dev/null +++ b/commands/compose/encrypt.go @@ -0,0 +1,44 @@ +package compose + +import ( + "errors" + "time" + + "git.sr.ht/~rjarry/aerc/widgets" +) + +type Encrypt struct{} + +func init() { + register(Encrypt{}) +} + +func (Encrypt) Aliases() []string { + return []string{"encrypt"} +} + +func (Encrypt) Complete(aerc *widgets.Aerc, args []string) []string { + return nil +} + +func (Encrypt) Execute(aerc *widgets.Aerc, args []string) error { + if len(args) != 1 { + return errors.New("Usage: encrypt") + } + + composer, _ := aerc.SelectedTab().(*widgets.Composer) + + composer.SetEncrypt(!composer.Encrypt()) + + var statusline string + + if composer.Encrypt() { + statusline = "Message will be encrypted." + } else { + statusline = "Message will not be encrypted." + } + + aerc.PushStatus(statusline, 10*time.Second) + + return nil +} diff --git a/lib/keystore.go b/lib/keystore.go index c211067..0b9d41a 100644 --- a/lib/keystore.go +++ b/lib/keystore.go @@ -53,6 +53,16 @@ func UnlockKeyring() { os.Remove(lockpath) } +func GetEntityByEmail(email string) (e *openpgp.Entity, err error) { + for _, entity := range Keyring { + ident := entity.PrimaryIdentity() + if ident != nil && ident.UserId.Email == email { + return entity, nil + } + } + return nil, fmt.Errorf("entity not found in keyring") +} + func GetSignerEntityByEmail(email string) (e *openpgp.Entity, err error) { for _, key := range Keyring.DecryptionKeys() { if key.Entity == nil { diff --git a/widgets/compose.go b/widgets/compose.go index 6b7f5cd..46d4025 100644 --- a/widgets/compose.go +++ b/widgets/compose.go @@ -52,6 +52,7 @@ type Composer struct { worker *types.Worker completer *completer.Completer sign bool + encrypt bool layout HeaderLayout focusable []ui.MouseableDrawableInteractive @@ -185,6 +186,15 @@ func (c *Composer) Sign() bool { return c.sign } +func (c *Composer) SetEncrypt(encrypt bool) *Composer { + c.encrypt = encrypt + return c +} + +func (c *Composer) Encrypt() bool { + return c.encrypt +} + // Note: this does not reload the editor. You must call this before the first // Draw() call. func (c *Composer) SetContents(reader io.Reader) *Composer { @@ -417,27 +427,83 @@ func getSenderEmail(c *Composer) (string, error) { return from.Address, nil } +func getRecipientsEmail(c *Composer) ([]string, error) { + h, err := c.PrepareHeader() + if err != nil { + return nil, errors.Wrap(err, "PrepareHeader") + } + + // collect all 'recipients' from header (to:, cc:, bcc:) + rcpts := make(map[string]bool) + for _, key := range []string{"to", "cc", "bcc"} { + list, err := h.AddressList(key) + if err != nil { + continue + } + for _, entry := range list { + if entry != nil { + rcpts[entry.Address] = true + } + } + } + + // return email addresses as string slice + results := []string{} + for email, _ := range rcpts { + results = append(results, email) + } + return results, nil +} + func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error { if err := c.reloadEmail(); err != nil { return err } - if c.sign { - - signer, err := getSigner(c) - if err != nil { - return err - } + if c.sign || c.encrypt { var signedHeader mail.Header signedHeader.SetContentType("text/plain", nil) var buf bytes.Buffer var cleartext io.WriteCloser + var err error - cleartext, err = pgpmail.Sign(&buf, header.Header.Header, signer, nil) - if err != nil { - return err + var signer *openpgp.Entity + if c.sign { + signer, err = getSigner(c) + if err != nil { + return err + } + } else { + signer = nil + } + + if c.encrypt { + var to []*openpgp.Entity + rcpts, err := getRecipientsEmail(c) + if err != nil { + return err + } + for _, rcpt := range rcpts { + toEntity, err := lib.GetEntityByEmail(rcpt) + if err != nil { + return errors.Wrap(err, "no key for "+rcpt) + } + to = append(to, toEntity) + } + cleartext, err = pgpmail.Encrypt(&buf, header.Header.Header, + to, signer, nil) + + if err != nil { + return err + } + } else { + cleartext, err = pgpmail.Sign(&buf, header.Header.Header, + signer, nil) + if err != nil { + return err + } } err = writeMsgImpl(c, &signedHeader, cleartext)