pgp: check for signing key before signing time

Check that the signing key exists when the user issues the :sign
command. The signing key ID will be displayed in the security status
also, allowing the user to see what key will be used to sign the
message.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Tested-by: Jens Grassel <jens@wegtam.com>
This commit is contained in:
Tim Culverhouse 2022-04-29 11:19:52 -05:00 committed by Robin Jarry
parent b29293d7b5
commit dbf52bb4b4
7 changed files with 93 additions and 7 deletions

View file

@ -28,7 +28,10 @@ func (Sign) Execute(aerc *widgets.Aerc, args []string) error {
composer, _ := aerc.SelectedTab().(*widgets.Composer) composer, _ := aerc.SelectedTab().(*widgets.Composer)
composer.SetSign(!composer.Sign()) err := composer.SetSign(!composer.Sign())
if err != nil {
return err
}
var statusline string var statusline string

View file

@ -19,6 +19,7 @@ type Provider interface {
ImportKeys(io.Reader) error ImportKeys(io.Reader) error
Init(*log.Logger) error Init(*log.Logger) error
Close() Close()
GetSignerKeyId(string) (string, error)
} }
func New(s string) Provider { func New(s string) Provider {

View file

@ -51,6 +51,10 @@ func (m *Mail) Sign(buf *bytes.Buffer, signer string, decryptKeys openpgp.Prompt
func (m *Mail) Close() {} func (m *Mail) Close() {}
func (m *Mail) GetSignerKeyId(s string) (string, error) {
return gpgbin.GetPrivateKeyId(s)
}
func handleSignatureError(e string) models.SignatureValidity { func handleSignatureError(e string) models.SignatureValidity {
if e == "gpg: missing public key" { if e == "gpg: missing public key" {
return models.UnknownEntity return models.UnknownEntity

View file

@ -77,6 +77,29 @@ func getIdentity(key uint64) string {
return "" return ""
} }
// getKeyId returns the 16 digit key id, if key exists
func getKeyId(s string, private bool) string {
cmd := exec.Command("gpg", "--with-colons", "--batch")
listArg := "--list-keys"
if private {
listArg = "--list-secret-keys"
}
cmd.Args = append(cmd.Args, listArg, s)
var outbuf strings.Builder
cmd.Stdout = &outbuf
cmd.Run()
out := strings.Split(outbuf.String(), "\n")
for _, line := range out {
if strings.HasPrefix(line, "fpr") {
flds := strings.Split(line, ":")
id := flds[9]
return id[len(id)-16:]
}
}
return ""
}
// longKeyToUint64 returns a uint64 version of the given key // longKeyToUint64 returns a uint64 version of the given key
func longKeyToUint64(key string) (uint64, error) { func longKeyToUint64(key string) (uint64, error) {
fpr := string(key[len(key)-16:]) fpr := string(key[len(key)-16:])

View file

@ -0,0 +1,13 @@
package gpgbin
import "fmt"
// GetPrivateKeyId runs gpg --list-secret-keys s
func GetPrivateKeyId(s string) (string, error) {
private := true
id := getKeyId(s, private)
if id == "" {
return "", fmt.Errorf("no private key found")
}
return id, nil
}

View file

@ -245,6 +245,24 @@ func (m *Mail) getSigner(signer string, decryptKeys openpgp.PromptFunction) (sig
return signerEntity, nil return signerEntity, nil
} }
func (m *Mail) GetSignerKeyId(s string) (string, error) {
var err error
var signerEntity *openpgp.Entity
switch strings.Contains(s, "@") {
case true:
signerEntity, err = m.getSignerEntityByEmail(s)
if err != nil {
return "", err
}
case false:
signerEntity, err = m.getSignerEntityByKeyId(s)
if err != nil {
return "", err
}
}
return signerEntity.PrimaryKey.KeyIdString(), nil
}
func handleSignatureError(e string) models.SignatureValidity { func handleSignatureError(e string) models.SignatureValidity {
if e == "openpgp: signature made by unknown entity" { if e == "openpgp: signature made by unknown entity" {
return models.UnknownEntity return models.UnknownEntity

View file

@ -176,10 +176,14 @@ func (c *Composer) Sent() bool {
return c.sent return c.sent
} }
func (c *Composer) SetSign(sign bool) *Composer { func (c *Composer) SetSign(sign bool) error {
c.sign = sign c.sign = sign
c.updateCrypto() err := c.updateCrypto()
return c if err != nil {
c.sign = !sign
return fmt.Errorf("Cannot sign message: %v", err)
}
return nil
} }
func (c *Composer) Sign() bool { func (c *Composer) Sign() bool {
@ -196,18 +200,36 @@ func (c *Composer) Encrypt() bool {
return c.encrypt return c.encrypt
} }
func (c *Composer) updateCrypto() { func (c *Composer) updateCrypto() error {
if c.crypto == nil { if c.crypto == nil {
c.crypto = newCryptoStatus(&c.config.Ui) c.crypto = newCryptoStatus(&c.config.Ui)
} }
var err error
// Check if signKey is empty so we only run this once
if c.sign && c.crypto.signKey == "" {
cp := c.aerc.Crypto
var s string
if c.acctConfig.PgpKeyId != "" {
s = c.acctConfig.PgpKeyId
} else {
s, err = getSenderEmail(c)
if err != nil {
return err
}
}
c.crypto.signKey, err = cp.GetSignerKeyId(s)
if err != nil {
return err
}
}
crHeight := 0 crHeight := 0
st := "" st := ""
switch { switch {
case c.sign && c.encrypt: case c.sign && c.encrypt:
st = "Sign & Encrypt" st = fmt.Sprintf("Sign (%s) & Encrypt", c.crypto.signKey)
crHeight = 1 crHeight = 1
case c.sign: case c.sign:
st = "Sign" st = fmt.Sprintf("Sign (%s)", c.crypto.signKey)
crHeight = 1 crHeight = 1
case c.encrypt: case c.encrypt:
st = "Encrypt" st = "Encrypt"
@ -224,6 +246,7 @@ func (c *Composer) updateCrypto() {
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)}, {Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
}) })
c.grid.AddChild(c.crypto).At(1, 0) c.grid.AddChild(c.crypto).At(1, 0)
return nil
} }
// Note: this does not reload the editor. You must call this before the first // Note: this does not reload the editor. You must call this before the first
@ -1062,6 +1085,7 @@ type cryptoStatus struct {
title string title string
status *ui.Text status *ui.Text
uiConfig *config.UIConfig uiConfig *config.UIConfig
signKey string
} }
func newCryptoStatus(uiConfig *config.UIConfig) *cryptoStatus { func newCryptoStatus(uiConfig *config.UIConfig) *cryptoStatus {