~sircmpwn/aerc

pgp: fail gracefully from email decryption v1 PROPOSED

Ray Ganardi: 4
 pgp: fail gracefully from email decryption
 feat(pgp): Add <ESC> to cancel password prompt
 feat(pgp): Show error message from pgp
 aerc: Refactor getpasswd dialog

 10 files changed, 110 insertions(+), 62 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/10692/mbox | git am -3
Learn more about email & git

[PATCH 1/4] pgp: fail gracefully from email decryption Export this patch

Aerc panics when there's an error on email decryption.
Instead, an error message should be shown.
---
 commands/account/view.go |  6 +++++-
 commands/msg/delete.go   |  6 +++++-
 commands/msgview/next.go |  6 +++++-
 lib/messageview.go       | 18 +++++++++++-------
 widgets/msglist.go       |  6 +++++-
 5 files changed, 31 insertions(+), 11 deletions(-)

diff --git a/commands/account/view.go b/commands/account/view.go
index d1b90ce..b421666 100644
--- a/commands/account/view.go
+++ b/commands/account/view.go
@@ -39,7 +39,11 @@ func (ViewMessage) Execute(aerc *widgets.Aerc, args []string) error {
		return nil
	}
	lib.NewMessageStoreView(msg, store, aerc.DecryptKeys,
		func(view lib.MessageView) {
		func(view lib.MessageView, err error) {
			if err != nil {
				aerc.PushError(err.Error())
				return
			}
			viewer := widgets.NewMessageViewer(acct, aerc.Config(), view)
			aerc.NewTab(viewer, msg.Envelope.Subject)
		})
diff --git a/commands/msg/delete.go b/commands/msg/delete.go
index 313e3ea..4bda8b9 100644
--- a/commands/msg/delete.go
+++ b/commands/msg/delete.go
@@ -66,7 +66,11 @@ func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
				return nil
			}
			lib.NewMessageStoreView(next, store, aerc.DecryptKeys,
				func(view lib.MessageView) {
				func(view lib.MessageView, err error) {
					if err != nil {
						aerc.PushError(err.Error())
						return
					}
					nextMv := widgets.NewMessageViewer(acct, aerc.Config(), view)
					aerc.ReplaceTab(mv, nextMv, next.Envelope.Subject)
				})
diff --git a/commands/msgview/next.go b/commands/msgview/next.go
index c218ad5..978cf10 100644
--- a/commands/msgview/next.go
+++ b/commands/msgview/next.go
@@ -38,7 +38,11 @@ func (NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
		return nil
	}
	lib.NewMessageStoreView(nextMsg, store, aerc.DecryptKeys,
		func(view lib.MessageView) {
		func(view lib.MessageView, err error) {
			if err != nil {
				aerc.PushError(err.Error())
				return
			}
			nextMv := widgets.NewMessageViewer(acct, aerc.Config(), view)
			aerc.ReplaceTab(mv, nextMv, nextMsg.Envelope.Subject)
		})
diff --git a/lib/messageview.go b/lib/messageview.go
index 59a1af6..384a947 100644
--- a/lib/messageview.go
+++ b/lib/messageview.go
@@ -59,7 +59,7 @@ type MessageStoreView struct {

func NewMessageStoreView(messageInfo *models.MessageInfo,
	store *MessageStore, decryptKeys openpgp.PromptFunction,
	cb func(MessageView)) {
	cb func(MessageView, error)) {

	msv := &MessageStoreView{messageInfo, store,
		nil, nil, messageInfo.BodyStructure}
@@ -69,26 +69,30 @@ func NewMessageStoreView(messageInfo *models.MessageInfo,
			reader := fm.Content.Reader
			pgpReader, err := pgpmail.Read(reader, Keyring, decryptKeys, nil)
			if err != nil {
				panic(err)
				cb(nil, err)
				return
			}
			msv.message, err = ioutil.ReadAll(pgpReader.MessageDetails.UnverifiedBody)
			if err != nil {
				panic(err)
				cb(nil, err)
				return
			}
			decrypted, err := message.Read(bytes.NewBuffer(msv.message))
			if err != nil {
				panic(err)
				cb(nil, err)
				return
			}
			bs, err := lib.ParseEntityStructure(decrypted)
			if err != nil {
				panic(err)
				cb(nil, err)
				return
			}
			msv.bodyStructure = bs
			msv.details = pgpReader.MessageDetails
			cb(msv)
			cb(msv, nil)
		})
	} else {
		cb(msv)
		cb(msv, nil)
	}
	store.Read([]uint32{messageInfo.Uid}, true, nil)
}
diff --git a/widgets/msglist.go b/widgets/msglist.go
index f36901f..5aedb44 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -166,7 +166,11 @@ func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) {
					return
				}
				lib.NewMessageStoreView(msg, store, ml.aerc.DecryptKeys,
					func(view lib.MessageView) {
					func(view lib.MessageView, err error) {
						if err != nil {
							ml.aerc.PushError(err.Error())
							return
						}
						viewer := NewMessageViewer(acct, ml.aerc.Config(), view)
						ml.aerc.NewTab(viewer, msg.Envelope.Subject)
					})
-- 
2.26.2

[PATCH 2/4] feat(pgp): Add <ESC> to cancel password prompt Export this patch

Previously there was no way to cancel the password prompt.
---
 widgets/aerc.go      | 18 +++++++++++-------
 widgets/getpasswd.go | 11 ++++++++---
 2 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/widgets/aerc.go b/widgets/aerc.go
index 23b8901..273777b 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -537,11 +537,11 @@ func (aerc *Aerc) CloseBackends() error {
	return returnErr
}

func (aerc *Aerc) GetPassword(title string, prompt string, cb func(string)) {
	aerc.getpasswd = NewGetPasswd(title, prompt, func(pw string) {
func (aerc *Aerc) GetPassword(title string, prompt string, cb func(string, error)) {
	aerc.getpasswd = NewGetPasswd(title, prompt, func(pw string, err error) {
		aerc.getpasswd = nil
		aerc.Invalidate()
		cb(pw)
		cb(pw, err)
	})
	aerc.getpasswd.OnInvalidate(func(_ ui.Drawable) {
		aerc.Invalidate()
@@ -553,7 +553,7 @@ func (aerc *Aerc) Initialize(ui *ui.UI) {
	aerc.ui = ui
}

func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) ([]byte, error) {
func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) (b []byte, err error) {
	// HACK HACK HACK
	for _, key := range keys {
		var ident *openpgp.Identity
@@ -561,14 +561,18 @@ func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) ([]byte, error
			break
		}
		aerc.GetPassword("Decrypt PGP private key",
			fmt.Sprintf("Enter password for %s (%8X)",
			fmt.Sprintf("Enter password for %s (%8X)\nPress <ESC> to cancel",
				ident.Name, key.PublicKey.KeyId),
			func(pass string) {
			func(pass string, e error) {
				if e != nil {
					err = e
					return
				}
				key.PrivateKey.Decrypt([]byte(pass))
			})
		for aerc.getpasswd != nil {
			aerc.ui.Tick()
		}
	}
	return nil, nil
	return nil, err
}
diff --git a/widgets/getpasswd.go b/widgets/getpasswd.go
index 08702c5..34f8b1f 100644
--- a/widgets/getpasswd.go
+++ b/widgets/getpasswd.go
@@ -1,6 +1,8 @@
package widgets

import (
	"fmt"

	"github.com/gdamore/tcell"

	"git.sr.ht/~sircmpwn/aerc/lib/ui"
@@ -8,13 +10,13 @@ import (

type GetPasswd struct {
	ui.Invalidatable
	callback func(string)
	callback func(string, error)
	title    string
	prompt   string
	input    *ui.TextInput
}

func NewGetPasswd(title string, prompt string, cb func(string)) *GetPasswd {
func NewGetPasswd(title string, prompt string, cb func(string, error)) *GetPasswd {
	getpasswd := &GetPasswd{
		callback: cb,
		title:    title,
@@ -46,7 +48,10 @@ func (gp *GetPasswd) Event(event tcell.Event) bool {
		switch event.Key() {
		case tcell.KeyEnter:
			gp.input.Focus(false)
			gp.callback(gp.input.String())
			gp.callback(gp.input.String(), nil)
		case tcell.KeyEsc:
			gp.input.Focus(false)
			gp.callback("", fmt.Errorf("no password provided"))
		default:
			gp.input.Event(event)
		}
-- 
2.26.2

[PATCH 3/4] feat(pgp): Show error message from pgp Export this patch

The error wasn't shown, making errors like wrong password being ignored
and the password is prompted again.
---
 widgets/aerc.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/widgets/aerc.go b/widgets/aerc.go
index 273777b..eb037df 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -568,7 +568,10 @@ func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) (b []byte, err
					err = e
					return
				}
				key.PrivateKey.Decrypt([]byte(pass))
				e = key.PrivateKey.Decrypt([]byte(pass))
				if e != nil {
					err = e
				}
			})
		for aerc.getpasswd != nil {
			aerc.ui.Tick()
-- 
2.26.2

[PATCH 4/4] aerc: Refactor getpasswd dialog Export this patch

Previously there's a hack for showing and hiding the dialog.

Change it to use channels to emulate async/await
---
 widgets/aerc.go    | 83 +++++++++++++++++++++++++++++-----------------
 widgets/pgpinfo.go | 13 ++------
 2 files changed, 56 insertions(+), 40 deletions(-)

diff --git a/widgets/aerc.go b/widgets/aerc.go
index eb037df..829873a 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -36,7 +36,7 @@ type Aerc struct {
	tabs        *ui.Tabs
	ui          *ui.UI
	beep        func() error
	getpasswd   *GetPasswd
	dialog      ui.DrawableInteractive
}

type Choice struct {
@@ -170,8 +170,8 @@ func (aerc *Aerc) Focus(focus bool) {

func (aerc *Aerc) Draw(ctx *ui.Context) {
	aerc.grid.Draw(ctx)
	if aerc.getpasswd != nil {
		aerc.getpasswd.Draw(ctx.Subcontext(4, ctx.Height()/2-2,
	if aerc.dialog != nil {
		aerc.dialog.Draw(ctx.Subcontext(4, ctx.Height()/2-2,
			ctx.Width()-8, 4))
	}
}
@@ -212,8 +212,8 @@ func (aerc *Aerc) simulate(strokes []config.KeyStroke) {
}

func (aerc *Aerc) Event(event tcell.Event) bool {
	if aerc.getpasswd != nil {
		return aerc.getpasswd.Event(event)
	if aerc.dialog != nil {
		return aerc.dialog.Event(event)
	}

	if aerc.focused != nil {
@@ -537,16 +537,42 @@ func (aerc *Aerc) CloseBackends() error {
	return returnErr
}

func (aerc *Aerc) GetPassword(title string, prompt string, cb func(string, error)) {
	aerc.getpasswd = NewGetPasswd(title, prompt, func(pw string, err error) {
		aerc.getpasswd = nil
		aerc.Invalidate()
		cb(pw, err)
	})
	aerc.getpasswd.OnInvalidate(func(_ ui.Drawable) {
func (aerc *Aerc) AddDialog(d ui.DrawableInteractive) {
	aerc.dialog = d
	aerc.dialog.OnInvalidate(func(_ ui.Drawable) {
		aerc.Invalidate()
	})
	aerc.Invalidate()
	return
}

func (aerc *Aerc) CloseDialog() {
	aerc.dialog = nil
	aerc.Invalidate()
	return
}


func (aerc *Aerc) GetPassword(title string, prompt string) (chText chan string, chErr chan error) {
	chText = make(chan string, 1)
	chErr = make(chan error, 1)
	getPasswd := NewGetPasswd(title, prompt, func(pw string, err error) {
		defer func() {
			close(chErr)
			close(chText)
			aerc.CloseDialog()
		}()
		if err != nil {
			chErr <- err
			return
		}
		chErr <- nil
		chText <- pw
		return
	})
	aerc.AddDialog(getPasswd)

	return
}

func (aerc *Aerc) Initialize(ui *ui.UI) {
@@ -554,27 +580,24 @@ func (aerc *Aerc) Initialize(ui *ui.UI) {
}

func (aerc *Aerc) DecryptKeys(keys []openpgp.Key, symmetric bool) (b []byte, err error) {
	// HACK HACK HACK
	for _, key := range keys {
		var ident *openpgp.Identity
		for _, ident = range key.Entity.Identities {
			break
		}
		aerc.GetPassword("Decrypt PGP private key",
		ident := key.Entity.PrimaryIdentity()
		chPass, chErr := aerc.GetPassword("Decrypt PGP private key",
			fmt.Sprintf("Enter password for %s (%8X)\nPress <ESC> to cancel",
				ident.Name, key.PublicKey.KeyId),
			func(pass string, e error) {
				if e != nil {
					err = e
					return
				}
				e = key.PrivateKey.Decrypt([]byte(pass))
				if e != nil {
					err = e
				ident.Name, key.PublicKey.KeyId))

		for {
			select {
			case err = <-chErr:
				if err != nil {
					return nil, err
				}
			})
		for aerc.getpasswd != nil {
			aerc.ui.Tick()
				pass := <-chPass
				err = key.PrivateKey.Decrypt([]byte(pass))
				return nil, err
			default:
				aerc.ui.Tick()
			}
		}
	}
	return nil, err
diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go
index dc03cf6..5da9141 100644
--- a/widgets/pgpinfo.go
+++ b/widgets/pgpinfo.go
@@ -41,11 +41,8 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
			p.details.SignatureError.Error())
	} else {
		entity := p.details.SignedBy.Entity
		var ident *openpgp.Identity
		// TODO: Pick identity more intelligently
		for _, ident = range entity.Identities {
			break
		}
		ident := entity.PrimaryIdentity()

		x := ctx.Printf(0, 0, validStyle, "✓ Authentic ")
		x += ctx.Printf(x, 0, tcell.StyleDefault,
			"Signature from %s (%8X)",
@@ -56,11 +53,7 @@ func (p *PGPInfo) DrawSignature(ctx *ui.Context) {
func (p *PGPInfo) DrawEncryption(ctx *ui.Context, y int) {
	validStyle := tcell.StyleDefault.Foreground(tcell.ColorGreen).Bold(true)
	entity := p.details.DecryptedWith.Entity
	var ident *openpgp.Identity
	// TODO: Pick identity more intelligently
	for _, ident = range entity.Identities {
		break
	}
	ident := entity.PrimaryIdentity()

	x := ctx.Printf(0, y, validStyle, "✓ Encrypted ")
	x += ctx.Printf(x, y, tcell.StyleDefault,
-- 
2.26.2
View this thread in the archives