summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--commands/compose/encrypt.go12
-rw-r--r--lib/crypto/crypto.go1
-rw-r--r--lib/crypto/gpg/gpg.go4
-rw-r--r--lib/crypto/gpg/gpgbin/keys.go10
-rw-r--r--lib/crypto/pgp/pgp.go8
-rw-r--r--lib/ui/textinput.go14
-rw-r--r--widgets/compose.go79
7 files changed, 107 insertions, 21 deletions
diff --git a/commands/compose/encrypt.go b/commands/compose/encrypt.go
index d63940b..3bd8ca4 100644
--- a/commands/compose/encrypt.go
+++ b/commands/compose/encrypt.go
@@ -2,7 +2,6 @@ package compose
import (
"errors"
- "time"
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -29,16 +28,5 @@ func (Encrypt) Execute(aerc *widgets.Aerc, args []string) error {
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/crypto/crypto.go b/lib/crypto/crypto.go
index cab9346..54a20e6 100644
--- a/lib/crypto/crypto.go
+++ b/lib/crypto/crypto.go
@@ -20,6 +20,7 @@ type Provider interface {
Init(*log.Logger) error
Close()
GetSignerKeyId(string) (string, error)
+ GetKeyId(string) (string, error)
}
func New(s string) Provider {
diff --git a/lib/crypto/gpg/gpg.go b/lib/crypto/gpg/gpg.go
index 457788d..fe32468 100644
--- a/lib/crypto/gpg/gpg.go
+++ b/lib/crypto/gpg/gpg.go
@@ -55,6 +55,10 @@ func (m *Mail) GetSignerKeyId(s string) (string, error) {
return gpgbin.GetPrivateKeyId(s)
}
+func (m *Mail) GetKeyId(s string) (string, error) {
+ return gpgbin.GetKeyId(s)
+}
+
func handleSignatureError(e string) models.SignatureValidity {
if e == "gpg: missing public key" {
return models.UnknownEntity
diff --git a/lib/crypto/gpg/gpgbin/keys.go b/lib/crypto/gpg/gpgbin/keys.go
index 660ce82..9c8b233 100644
--- a/lib/crypto/gpg/gpgbin/keys.go
+++ b/lib/crypto/gpg/gpgbin/keys.go
@@ -11,3 +11,13 @@ func GetPrivateKeyId(s string) (string, error) {
}
return id, nil
}
+
+// GetKeyId runs gpg --list-keys s
+func GetKeyId(s string) (string, error) {
+ private := false
+ id := getKeyId(s, private)
+ if id == "" {
+ return "", fmt.Errorf("no public key found")
+ }
+ return id, nil
+}
diff --git a/lib/crypto/pgp/pgp.go b/lib/crypto/pgp/pgp.go
index e0c5671..f0f3f65 100644
--- a/lib/crypto/pgp/pgp.go
+++ b/lib/crypto/pgp/pgp.go
@@ -263,6 +263,14 @@ func (m *Mail) GetSignerKeyId(s string) (string, error) {
return signerEntity.PrimaryKey.KeyIdString(), nil
}
+func (m *Mail) GetKeyId(s string) (string, error) {
+ entity, err := m.getEntityByEmail(s)
+ if err != nil {
+ return "", err
+ }
+ return entity.PrimaryKey.KeyIdString(), nil
+}
+
func handleSignatureError(e string) models.SignatureValidity {
if e == "openpgp: signature made by unknown entity" {
return models.UnknownEntity
diff --git a/lib/ui/textinput.go b/lib/ui/textinput.go
index aa15300..0a331dc 100644
--- a/lib/ui/textinput.go
+++ b/lib/ui/textinput.go
@@ -26,6 +26,7 @@ type TextInput struct {
scroll int
text []rune
change []func(ti *TextInput)
+ focusLost []func(ti *TextInput)
tabcomplete func(s string) ([]string, string)
completions []string
prefix string
@@ -157,6 +158,9 @@ func (ti *TextInput) MouseEvent(localX int, localY int, event tcell.Event) {
}
func (ti *TextInput) Focus(focus bool) {
+ if ti.focus && !focus {
+ ti.onFocusLost()
+ }
ti.focus = focus
if focus && ti.ctx != nil {
cells := runewidth.StringWidth(string(ti.text[:ti.index]))
@@ -274,6 +278,12 @@ func (ti *TextInput) onChange() {
}
}
+func (ti *TextInput) onFocusLost() {
+ for _, focusLost := range ti.focusLost {
+ focusLost(ti)
+ }
+}
+
func (ti *TextInput) updateCompletions() {
if ti.tabcomplete == nil {
// no completer
@@ -304,6 +314,10 @@ func (ti *TextInput) OnChange(onChange func(ti *TextInput)) {
ti.change = append(ti.change, onChange)
}
+func (ti *TextInput) OnFocusLost(onFocusLost func(ti *TextInput)) {
+ ti.focusLost = append(ti.focusLost, onFocusLost)
+}
+
func (ti *TextInput) Event(event tcell.Event) bool {
switch event := event.(type) {
case *tcell.EventKey:
diff --git a/widgets/compose.go b/widgets/compose.go
index 5dab429..49627fc 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -198,8 +198,21 @@ func (c *Composer) Sign() bool {
}
func (c *Composer) SetEncrypt(encrypt bool) *Composer {
- c.encrypt = encrypt
- c.updateCrypto()
+ if !encrypt {
+ c.encrypt = encrypt
+ c.updateCrypto()
+ return c
+ }
+ // Check on any attempt to encrypt, and any lost focus of "to", "cc", or
+ // "bcc" field. Use OnFocusLost instead of OnChange to limit keyring checks
+ c.encrypt = c.checkEncryptionKeys("")
+ if c.crypto.setEncOneShot {
+ // Prevent registering a lot of callbacks
+ c.OnFocusLost("to", c.checkEncryptionKeys)
+ c.OnFocusLost("cc", c.checkEncryptionKeys)
+ c.OnFocusLost("bcc", c.checkEncryptionKeys)
+ c.crypto.setEncOneShot = false
+ }
return c
}
@@ -365,6 +378,15 @@ func (c *Composer) OnHeaderChange(header string, fn func(subject string)) {
}
}
+// OnFocusLost registers an OnFocusLost callback for the specified header.
+func (c *Composer) OnFocusLost(header string, fn func(input string) bool) {
+ if editor, ok := c.editors[strings.ToLower(header)]; ok {
+ editor.OnFocusLost(func() {
+ fn(editor.input.String())
+ })
+ }
+}
+
func (c *Composer) OnClose(fn func(composer *Composer)) {
c.onClose = append(c.onClose, fn)
}
@@ -984,6 +1006,12 @@ func (he *headerEditor) OnChange(fn func()) {
})
}
+func (he *headerEditor) OnFocusLost(fn func()) {
+ he.input.OnFocusLost(func(_ *ui.TextInput) {
+ fn()
+ })
+}
+
type reviewMessage struct {
composer *Composer
grid *ui.Grid
@@ -1090,18 +1118,21 @@ func (rm *reviewMessage) Draw(ctx *ui.Context) {
}
type cryptoStatus struct {
- title string
- status *ui.Text
- uiConfig *config.UIConfig
- signKey string
+ title string
+ status *ui.Text
+ uiConfig *config.UIConfig
+ signKey string
+ setEncOneShot bool
}
func newCryptoStatus(uiConfig *config.UIConfig) *cryptoStatus {
defaultStyle := uiConfig.GetStyle(config.STYLE_DEFAULT)
return &cryptoStatus{
- title: "Security",
- status: ui.NewText("", defaultStyle),
- uiConfig: uiConfig,
+ title: "Security",
+ status: ui.NewText("", defaultStyle),
+ uiConfig: uiConfig,
+ signKey: "",
+ setEncOneShot: true,
}
}
@@ -1124,3 +1155,33 @@ func (cs *cryptoStatus) OnInvalidate(fn func(ui.Drawable)) {
fn(cs)
})
}
+
+func (c *Composer) checkEncryptionKeys(_ string) bool {
+ rcpts, err := getRecipientsEmail(c)
+ if err != nil {
+ // checkEncryptionKeys gets registered as a callback and must
+ // explicitly call c.SetEncrypt(false) when encryption is not possible
+ c.SetEncrypt(false)
+ st := fmt.Sprintf("Cannot encrypt: %v", err)
+ c.aerc.statusline.PushError(st)
+ return false
+ }
+ var mk []string
+ for _, rcpt := range rcpts {
+ key, err := c.aerc.Crypto.GetKeyId(rcpt)
+ if err != nil || key == "" {
+ mk = append(mk, rcpt)
+ }
+ }
+ if len(mk) > 0 {
+ c.SetEncrypt(false)
+ st := fmt.Sprintf("Cannot encrypt, missing keys: %s", strings.Join(mk, ", "))
+ c.aerc.statusline.PushError(st)
+ return false
+ }
+ // If callbacks were registered, encrypt will be set when user removes
+ // recipients with missing keys
+ c.encrypt = true
+ c.updateCrypto()
+ return true
+}