summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/compose/sign.go44
-rw-r--r--lib/keystore.go14
-rw-r--r--widgets/compose.go113
3 files changed, 154 insertions, 17 deletions
diff --git a/commands/compose/sign.go b/commands/compose/sign.go
new file mode 100644
index 0000000..eb985e9
--- /dev/null
+++ b/commands/compose/sign.go
@@ -0,0 +1,44 @@
+package compose
+
+import (
+ "errors"
+ "time"
+
+ "git.sr.ht/~rjarry/aerc/widgets"
+)
+
+type Sign struct{}
+
+func init() {
+ register(Sign{})
+}
+
+func (Sign) Aliases() []string {
+ return []string{"sign"}
+}
+
+func (Sign) Complete(aerc *widgets.Aerc, args []string) []string {
+ return nil
+}
+
+func (Sign) Execute(aerc *widgets.Aerc, args []string) error {
+ if len(args) != 1 {
+ return errors.New("Usage: sign")
+ }
+
+ composer, _ := aerc.SelectedTab().(*widgets.Composer)
+
+ composer.SetSign(!composer.Sign())
+
+ var statusline string
+
+ if composer.Sign() {
+ statusline = "Message will be signed."
+ } else {
+ statusline = "Message will not be signed."
+ }
+
+ aerc.PushStatus(statusline, 10*time.Second)
+
+ return nil
+}
diff --git a/lib/keystore.go b/lib/keystore.go
index df048f4..c211067 100644
--- a/lib/keystore.go
+++ b/lib/keystore.go
@@ -1,6 +1,7 @@
package lib
import (
+ "fmt"
"io"
"os"
"path"
@@ -52,6 +53,19 @@ func UnlockKeyring() {
os.Remove(lockpath)
}
+func GetSignerEntityByEmail(email string) (e *openpgp.Entity, err error) {
+ for _, key := range Keyring.DecryptionKeys() {
+ if key.Entity == nil {
+ continue
+ }
+ ident := key.Entity.PrimaryIdentity()
+ if ident != nil && ident.UserId.Email == email {
+ return key.Entity, nil
+ }
+ }
+ return nil, fmt.Errorf("entity not found in keyring")
+}
+
func ImportKeys(r io.Reader) error {
keys, err := openpgp.ReadKeyRing(r)
if err != nil {
diff --git a/widgets/compose.go b/widgets/compose.go
index 5ca0932..6b7f5cd 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -17,6 +17,7 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/emersion/go-message/mail"
+ "github.com/emersion/go-pgpmail"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
"github.com/mitchellh/go-homedir"
@@ -24,6 +25,7 @@ import (
"git.sr.ht/~rjarry/aerc/completer"
"git.sr.ht/~rjarry/aerc/config"
+ "git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/lib/format"
"git.sr.ht/~rjarry/aerc/lib/templates"
"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -49,6 +51,7 @@ type Composer struct {
review *reviewMessage
worker *types.Worker
completer *completer.Completer
+ sign bool
layout HeaderLayout
focusable []ui.MouseableDrawableInteractive
@@ -173,6 +176,15 @@ func (c *Composer) Sent() bool {
return c.sent
}
+func (c *Composer) SetSign(sign bool) *Composer {
+ c.sign = sign
+ return c
+}
+
+func (c *Composer) Sign() bool {
+ return c.sign
+}
+
// Note: this does not reload the editor. You must call this before the first
// Draw() call.
func (c *Composer) SetContents(reader io.Reader) *Composer {
@@ -393,34 +405,74 @@ func (c *Composer) PrepareHeader() (*mail.Header, error) {
return c.header, nil
}
+func getSenderEmail(c *Composer) (string, error) {
+ // add the from: field also to the 'recipients' list
+ if c.acctConfig.From == "" {
+ return "", errors.New("No 'From' configured for this account")
+ }
+ from, err := mail.ParseAddress(c.acctConfig.From)
+ if err != nil {
+ return "", errors.Wrap(err, "ParseAddress(config.From)")
+ }
+ return from.Address, nil
+}
+
func (c *Composer) WriteMessage(header *mail.Header, writer io.Writer) error {
if err := c.reloadEmail(); err != nil {
return err
}
- if len(c.attachments) == 0 {
- // don't create a multipart email if we only have text
- return writeInlineBody(header, c.email, writer)
- }
+ if c.sign {
- // otherwise create a multipart email,
- // with a multipart/alternative part for the text
- w, err := mail.CreateWriter(writer, *header)
- if err != nil {
- return errors.Wrap(err, "CreateWriter")
- }
- defer w.Close()
+ signer, err := getSigner(c)
+ if err != nil {
+ return err
+ }
- if err := writeMultipartBody(c.email, w); err != nil {
- return errors.Wrap(err, "writeMultipartBody")
- }
+ var signedHeader mail.Header
+ signedHeader.SetContentType("text/plain", nil)
+
+ var buf bytes.Buffer
+ var cleartext io.WriteCloser
- for _, a := range c.attachments {
- if err := writeAttachment(a, w); err != nil {
- return errors.Wrap(err, "writeAttachment")
+ cleartext, err = pgpmail.Sign(&buf, header.Header.Header, signer, nil)
+ if err != nil {
+ return err
}
+
+ err = writeMsgImpl(c, &signedHeader, cleartext)
+ if err != nil {
+ return err
+ }
+ cleartext.Close()
+ io.Copy(writer, &buf)
+ return nil
+
+ } else {
+ return writeMsgImpl(c, header, writer)
}
+}
+func writeMsgImpl(c *Composer, header *mail.Header, writer io.Writer) error {
+ if len(c.attachments) == 0 {
+ // no attachements
+ return writeInlineBody(header, c.email, writer)
+ } else {
+ // with attachements
+ w, err := mail.CreateWriter(writer, *header)
+ if err != nil {
+ return errors.Wrap(err, "CreateWriter")
+ }
+ if err := writeMultipartBody(c.email, w); err != nil {
+ return errors.Wrap(err, "writeMultipartBody")
+ }
+ for _, a := range c.attachments {
+ if err := writeAttachment(a, w); err != nil {
+ return errors.Wrap(err, "writeAttachment")
+ }
+ }
+ w.Close()
+ }
return nil
}
@@ -885,3 +937,30 @@ func (rm *reviewMessage) OnInvalidate(fn func(ui.Drawable)) {
func (rm *reviewMessage) Draw(ctx *ui.Context) {
rm.grid.Draw(ctx)
}
+
+func getSigner(c *Composer) (signer *openpgp.Entity, err error) {
+ signerEmail, err := getSenderEmail(c)
+ if err != nil {
+ return nil, err
+ }
+ signer, err = lib.GetSignerEntityByEmail(signerEmail)
+ if err != nil {
+ return nil, err
+ }
+
+ key, ok := signer.SigningKey(time.Now())
+ if !ok {
+ return nil, fmt.Errorf("no signing key found for %s", signerEmail)
+ }
+
+ if !key.PrivateKey.Encrypted {
+ return signer, nil
+ }
+
+ _, err = c.aerc.DecryptKeys([]openpgp.Key{key}, false)
+ if err != nil {
+ return nil, err
+ }
+
+ return signer, nil
+}