~rjarry/aerc-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
11 3

[PATCH aerc] pgp: PGP/MIME encryption and signing

Details
Message ID
<20211222150834.425806-1-koni.marti@gmail.com>
DKIM signature
pass
Download raw message
Patch: +258 -45
implements PGP/MIME encryption and signing of outgoing emails using the
keys from the internal keystore. The heavy lifiting is done in the go-pgpmail
package that provides the Encrypt() and Sign() functions.

The Sign() function needs access to the private key of the signer and the
Encrypt() function requires also the public keys of all recipients. The
private and public keys should thus be stored in aerc's keystore.

Public and private keys can be exported from gpg into the aerc keystore as
follows:
$ gpg --export > ~/.local/share/aerc/keyring.asc
$ gpg --export-secret-keys  >> ~/.local/share/aerc/keyring.asc

The exact location of the keyring file depends on the xdg.DataHome()
variable, but it's usually at ~/.local/share/aerc.

The github.com/x/crypto package has been consistently replaced by the
ProtonMail/go-crypto fork.

The PGP encryption (none, encryption or signing) can be enabled with the ':pgp'
command at the review window before sending the email. The command expects one
argument that can either be "none", "encrypt", or "sign". Default is none.

Replaces this patch https://lists.sr.ht/~rjarry/aerc-devel/patches/27550
Fixes ~rjarry/aerc/6

Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
 commands/compose/pgp.go |  45 +++++++++
 go.mod                  |   5 +-
 go.sum                  |  21 +----
 lib/keystore.go         |  28 +++++-
 lib/messageview.go      |   2 +-
 widgets/aerc.go         |   2 +-
 widgets/compose.go      | 196 +++++++++++++++++++++++++++++++++++-----
 widgets/pgpinfo.go      |   4 +-
 8 files changed, 258 insertions(+), 45 deletions(-)
 create mode 100644 commands/compose/pgp.go

diff --git a/commands/compose/pgp.go b/commands/compose/pgp.go
new file mode 100644
index 0000000..90913ce
--- /dev/null
+++ b/commands/compose/pgp.go
@@ -0,0 +1,45 @@
package compose

import (
	"errors"
	"strings"
	"time"

	"git.sr.ht/~rjarry/aerc/widgets"
)

type PGP struct{}

func init() {
	register(PGP{})
}

func (PGP) Aliases() []string {
	return []string{"pgp"}
}

func (PGP) Complete(aerc *widgets.Aerc, args []string) []string {
	return nil
}

func (PGP) Execute(aerc *widgets.Aerc, args []string) error {
	if len(args) != 2 {
		return errors.New("Usage: pgp [none|encrypt|sign]")
	}
	composer, _ := aerc.SelectedTab().(*widgets.Composer)

	var enc widgets.PGPEncryption
	switch arg := args[1]; {
	case strings.HasPrefix("encrypt", arg):
		enc = widgets.PGPEncrypt
		aerc.PushStatus("Message will be PGP encrypted...", 10*time.Second)
	case strings.HasPrefix("sign", arg):
		enc = widgets.PGPSign
		aerc.PushStatus("Message will be PGP signed...", 10*time.Second)
	default:
		enc = widgets.PGPNone
		aerc.PushStatus("No PGP encryption...", 10*time.Second)
	}
	composer.SetPGPEncryption(enc)
	return nil
}
diff --git a/go.mod b/go.mod
index 181f6a9..3a18379 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13

require (
	git.sr.ht/~sircmpwn/getopt v1.0.0
	github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab
	github.com/creack/pty v1.1.17
	github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
	github.com/ddevault/go-libvterm v0.0.0-20190526194226-b7d861da3810
@@ -11,7 +12,7 @@ require (
	github.com/emersion/go-imap-sortthread v1.2.0
	github.com/emersion/go-maildir v0.2.0
	github.com/emersion/go-message v0.15.0
	github.com/emersion/go-pgpmail v0.1.0
	github.com/emersion/go-pgpmail v0.2.0
	github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac
	github.com/emersion/go-smtp v0.15.0
	github.com/fsnotify/fsnotify v1.5.1
@@ -31,7 +32,7 @@ require (
	github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab
	github.com/stretchr/testify v1.4.0
	github.com/zenhack/go.notmuch v0.0.0-20211022191430-4d57e8ad2a8b
	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
	golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
	golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5
	golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 // indirect
diff --git a/go.sum b/go.sum
index 2635c9c..00e4036 100644
--- a/go.sum
+++ b/go.sum
@@ -37,6 +37,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 h1:JW27/kGLQzeM1Fxg5YQhdkTEAU7HIAHMgSag35zVTnY=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/brunnre8/go.notmuch v0.0.0-20201126061756-caa2daf7093c h1:dh58QrW3/S/aCnQPFoeRRE9zMauKooDFd5zh1dLtxXs=
github.com/brunnre8/go.notmuch v0.0.0-20201126061756-caa2daf7093c/go.mod h1:zJtFvR3NinVdmBiLyB4MyXKmqyVfZEb2cK97ISfTgV8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -64,17 +67,14 @@ github.com/emersion/go-maildir v0.2.0/go.mod h1:I2j27lND/SRLgxROe50Vam81MSaqPFvJ
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
github.com/emersion/go-pgpmail v0.1.0 h1:+NuU9UtGnmmKvmI8evxmTAPXfUod6Gbf2uYT7rxTZ7w=
github.com/emersion/go-pgpmail v0.1.0/go.mod h1:9Sy6uI+dlTN56tcWMtBQHqNDeea27xYItaiZ/3XC76g=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
github.com/emersion/go-pgpmail v0.2.0 h1:BU9kEGQcDVXi6n0v3JBsWAikyo63xsUGZ1lnVaWa6ks=
github.com/emersion/go-pgpmail v0.2.0/go.mod h1:8mQ8Rpn+w28DDaiP8HvJuZjSAymaWr87K3zA/bwwkU0=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
@@ -108,7 +108,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -161,11 +160,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -191,7 +188,6 @@ github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetP
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -251,7 +247,6 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -267,7 +262,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -296,7 +290,6 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -312,7 +305,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -392,7 +384,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -449,11 +440,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/lib/keystore.go b/lib/keystore.go
index dcdbd74..0b9d41a 100644
--- a/lib/keystore.go
+++ b/lib/keystore.go
@@ -1,13 +1,14 @@
package lib

import (
	"fmt"
	"io"
	"os"
	"path"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/ProtonMail/go-crypto/openpgp/packet"
	"github.com/kyoh86/xdg"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/packet"
)

var (
@@ -52,6 +53,29 @@ 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 {
			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/lib/messageview.go b/lib/messageview.go
index 532d2c8..8db7994 100644
--- a/lib/messageview.go
+++ b/lib/messageview.go
@@ -5,10 +5,10 @@ import (
	"io"
	"io/ioutil"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message"
	_ "github.com/emersion/go-message/charset"
	"github.com/emersion/go-pgpmail"
	"golang.org/x/crypto/openpgp"

	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/lib"
diff --git a/widgets/aerc.go b/widgets/aerc.go
index b84dd87..3c52f7e 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -9,10 +9,10 @@ import (
	"strings"
	"time"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/google/shlex"
	"golang.org/x/crypto/openpgp"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
diff --git a/widgets/compose.go b/widgets/compose.go
index d3e57c5..ff20dc0 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -15,7 +15,9 @@ import (
	"strings"
	"time"

	"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"
@@ -23,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"
@@ -30,6 +33,14 @@ import (
	"git.sr.ht/~rjarry/aerc/worker/types"
)

type PGPEncryption int

const (
	PGPNone PGPEncryption = iota
	PGPEncrypt
	PGPSign
)

type Composer struct {
	editors map[string]*headerEditor // indexes in lower case (from / cc / bcc)
	header  *mail.Header
@@ -40,14 +51,15 @@ type Composer struct {
	acct       *AccountView
	aerc       *Aerc

	attachments []string
	editor      *Terminal
	email       *os.File
	grid        *ui.Grid
	heditors    *ui.Grid // from, to, cc display a user can jump to
	review      *reviewMessage
	worker      *types.Worker
	completer   *completer.Completer
	attachments   []string
	editor        *Terminal
	email         *os.File
	grid          *ui.Grid
	heditors      *ui.Grid // from, to, cc display a user can jump to
	review        *reviewMessage
	worker        *types.Worker
	completer     *completer.Completer
	pgpencryption PGPEncryption

	layout    HeaderLayout
	focusable []ui.MouseableDrawableInteractive
@@ -172,6 +184,10 @@ func (c *Composer) Sent() bool {
	return c.sent
}

func (c *Composer) SetPGPEncryption(enc PGPEncryption) {
	c.pgpencryption = enc
}

// Note: this does not reload the editor. You must call this before the first
// Draw() call.
func (c *Composer) SetContents(reader io.Reader) *Composer {
@@ -392,34 +408,127 @@ 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 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 len(c.attachments) == 0 {
		// don't create a multipart email if we only have text
		return writeInlineBody(header, c.email, writer)
	// PGPNone
	if c.pgpencryption == PGPNone {
		return writeMsgImpl(c, header, writer)
	}

	// otherwise create a multipart email,
	// with a multipart/alternative part for the text
	w, err := mail.CreateWriter(writer, *header)
	// PGPEncrypt & PGPSign
	signer, err := getSigner(c)
	if err != nil {
		return errors.Wrap(err, "CreateWriter")
		return err
	}
	defer w.Close()

	if err := writeMultipartBody(c.email, w); err != nil {
		return errors.Wrap(err, "writeMultipartBody")
	var signedHeader mail.Header
	signedHeader.SetContentType("text/plain", nil)

	to := []*openpgp.Entity{}
	if c.pgpencryption == PGPEncrypt {
		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)
		}
	}

	for _, a := range c.attachments {
		if err := writeAttachment(a, w); err != nil {
			return errors.Wrap(err, "writeAttachment")
	var buf bytes.Buffer
	var cleartext io.WriteCloser

	if c.pgpencryption == PGPEncrypt {
		// PGPEncrypt
		cleartext, err = pgpmail.Encrypt(&buf, header.Header.Header, to, signer, nil)
		if err != nil {
			return err
		}
	} else if c.pgpencryption == PGPSign {
		// PGPSign
		cleartext, err = pgpmail.Sign(&buf, header.Header.Header, signer, nil)
		if err != nil {
			return err
		}
	} else {
		return fmt.Errorf("implementation error in pgpencryption")
	}
	err = writeMsgImpl(c, &signedHeader, cleartext)
	if err != nil {
		return err
	}
	cleartext.Close()
	io.Copy(writer, &buf)
	return nil
}

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
}

@@ -884,3 +993,48 @@ 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
	}

	if signer.PrivateKey == nil {
		return nil, fmt.Errorf("no private key for signer")
	}

	if !signer.PrivateKey.Encrypted {
		return signer, nil
	}

	// decrypt key with password
	// FIXME: can we combine this with aerc.DecryptKeys()?
	var pass string
	ident := signer.PrimaryIdentity()
	chPass, chErr := c.aerc.GetPassword("Decrypt PGP private key",
		fmt.Sprintf("Enter password for %s (%8X)\nPress <ESC> to cancel",
			ident.Name, signer.PrimaryKey.KeyId))
	for {
		select {
		case err = <-chErr:
			if err != nil {
				goto exitWithError
			}
			pass = <-chPass
			err = signer.PrivateKey.Decrypt([]byte(pass))
			if err == nil {
				return signer, nil
			}
		default:
			c.aerc.ui.Tick()
		}
	}

exitWithError:
	return nil, fmt.Errorf("no signer found")
}
diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go
index febf29a..6c07ed9 100644
--- a/widgets/pgpinfo.go
+++ b/widgets/pgpinfo.go
@@ -6,8 +6,8 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"

	"golang.org/x/crypto/openpgp"
	pgperrors "golang.org/x/crypto/openpgp/errors"
	"github.com/ProtonMail/go-crypto/openpgp"
	pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors"
)

type PGPInfo struct {
-- 
2.34.1

[aerc/patches/.build.yml] build success

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CGLXCJVYJRI8.2M6M5Y4CLWOYT@cirno2>
In-Reply-To
<20211222150834.425806-1-koni.marti@gmail.com> (view parent)
DKIM signature
missing
Download raw message
aerc/patches/.build.yml: SUCCESS in 58s

[pgp: PGP/MIME encryption and signing][0] from [Koni Marti][1]

[0]: https://lists.sr.ht/~rjarry/aerc-devel/patches/27588
[1]: koni.marti@gmail.com

✓ #654840 SUCCESS aerc/patches/.build.yml https://builds.sr.ht/~rjarry/job/654840
Details
Message ID
<CGR4YRX7ZVWD.JOAYGK91MCY1@diabtop>
In-Reply-To
<20211222150834.425806-1-koni.marti@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Hi Koni,

Sorry not to have replied earlier. The holidays season is slowing me
down a bit :)

Koni Marti, Dec 22, 2021 at 16:08:
> implements PGP/MIME encryption and signing of outgoing emails using the
> keys from the internal keystore. The heavy lifiting is done in the go-pgpmail
> package that provides the Encrypt() and Sign() functions.
>
> The Sign() function needs access to the private key of the signer and the
> Encrypt() function requires also the public keys of all recipients. The
> private and public keys should thus be stored in aerc's keystore.
>
> Public and private keys can be exported from gpg into the aerc keystore as
> follows:
> $ gpg --export > ~/.local/share/aerc/keyring.asc
> $ gpg --export-secret-keys  >> ~/.local/share/aerc/keyring.asc
>
> The exact location of the keyring file depends on the xdg.DataHome()
> variable, but it's usually at ~/.local/share/aerc.
>
> The github.com/x/crypto package has been consistently replaced by the
> ProtonMail/go-crypto fork.
>
> The PGP encryption (none, encryption or signing) can be enabled with the ':pgp'
> command at the review window before sending the email. The command expects one
> argument that can either be "none", "encrypt", or "sign". Default is none.

What is the purpose of the "none" argument? I would prefer two separate
commands to sign/encrypt instead of a single "pgp" command that takes an
argument.

> Replaces this patch https://lists.sr.ht/~rjarry/aerc-devel/patches/27550

Next time, it would help if you added an In-Reply-To header referencing
the original Message-ID. I have set the other patch as SUPERSEDED.

> Fixes ~rjarry/aerc/6

Please use standard git trailers syntax with the full ticket url.

https://man.sr.ht/git.sr.ht/#referencing-tickets-in-git-commit-messages

Could you split this patch in three parts:

1. Use ProtonMail/go-crypto instead of github.com/x/crypto.
2. Implement signature via a dedicated "sign" command.
3. Implement encryption via a dedicated "encrypt" command.

One final question: what about smartcard support? I did not get any
replies on my comments on this patch:

https://lists.sr.ht/~sircmpwn/aerc/patches/27092

Maybe you could follow up on this? I would really prefer to avoid
a separate keyring and benefit from smartcard support. I believe this is
how {,neo}mutt does it. Maybe there is a libgpgme binding for go?

Thanks!
Details
Message ID
<CGS5RAIR6AKT.1XEJD1LJS2XEG@moon2>
In-Reply-To
<CGR4YRX7ZVWD.JOAYGK91MCY1@diabtop> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
> Sorry not to have replied earlier. The holidays season is slowing me
> down a bit :)

All good. Same here!

> What is the purpose of the "none" argument? I would prefer two separate
> commands to sign/encrypt instead of a single "pgp" command that takes an
> argument.

"None" was meant to unset the flags set be encrypt or sign. However, I
see your point for two separate commands and will adjust the code
accordingly.

> Next time, it would help if you added an In-Reply-To header referencing
> the original Message-ID. I have set the other patch as SUPERSEDED.

Yes, thanks.

> Please use standard git trailers syntax with the full ticket url.

My bad. Will fix that.

> Could you split this patch in three parts:
>
> 1. Use ProtonMail/go-crypto instead of github.com/x/crypto.
> 2. Implement signature via a dedicated "sign" command.
> 3. Implement encryption via a dedicated "encrypt" command.

Yes, I will submit a v2 patch containing the three commits in due
course.

> One final question: what about smartcard support? I did not get any
> replies on my comments on this patch:
>
> https://lists.sr.ht/~sircmpwn/aerc/patches/27092
>
> Maybe you could follow up on this? I would really prefer to avoid
> a separate keyring and benefit from smartcard support. I believe this is
> how {,neo}mutt does it. Maybe there is a libgpgme binding for go?

The PGP/MIME implementation in this patch is pretty keyring
agnostic in the sense that it doesn't matter where the keys come from 
(as long as they are openpgp keys).
I would thus assume that the code still works with a different keyring
implementation. I cannot comment on the smartcard support though, 
because I'm not familiar with it at all.

Right now I use the internal keyring because the code infrastructure is
already in place for the signature verification in lib/messageview.go. With
this patch, my aim was to merely close the offering of a full PGP/MIME
implemenation of encryption, signing and verification.

The internal keyring is not really documented (as far as I can tell), 
but it is very straightforward to populate it with just two exports from gpg.
This might be already useful for a lot of users or amateuers like
me.

I would need to look more into neomutt's keyring and libgpgme to provide
a more detailed answer to the overall keyring topic.

[PATCH aerc v2 1/3] pgp: update openpgp packages (go-crypto and go-pgpmail)

Details
Message ID
<20211230092509.211489-1-koni.marti@gmail.com>
In-Reply-To
<20211222150834.425806-1-koni.marti@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +15 -24
Replaces golang.org/x/crypto with github.com/ProtonMail/go-crypto
consistently and updates go-pgpmail to v0.2.0

Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
 go.mod             |  5 +++--
 go.sum             | 21 +++++----------------
 lib/keystore.go    |  4 ++--
 lib/messageview.go |  2 +-
 widgets/aerc.go    |  2 +-
 widgets/compose.go |  1 +
 widgets/pgpinfo.go |  4 ++--
 7 files changed, 15 insertions(+), 24 deletions(-)

diff --git a/go.mod b/go.mod
index 181f6a9..3a18379 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.13

require (
	git.sr.ht/~sircmpwn/getopt v1.0.0
	github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab
	github.com/creack/pty v1.1.17
	github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
	github.com/ddevault/go-libvterm v0.0.0-20190526194226-b7d861da3810
@@ -11,7 +12,7 @@ require (
	github.com/emersion/go-imap-sortthread v1.2.0
	github.com/emersion/go-maildir v0.2.0
	github.com/emersion/go-message v0.15.0
	github.com/emersion/go-pgpmail v0.1.0
	github.com/emersion/go-pgpmail v0.2.0
	github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac
	github.com/emersion/go-smtp v0.15.0
	github.com/fsnotify/fsnotify v1.5.1
@@ -31,7 +32,7 @@ require (
	github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab
	github.com/stretchr/testify v1.4.0
	github.com/zenhack/go.notmuch v0.0.0-20211022191430-4d57e8ad2a8b
	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
	golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
	golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
	golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5
	golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 // indirect
diff --git a/go.sum b/go.sum
index 2635c9c..00e4036 100644
--- a/go.sum
+++ b/go.sum
@@ -37,6 +37,9 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3 h1:JW27/kGLQzeM1Fxg5YQhdkTEAU7HIAHMgSag35zVTnY=
github.com/ProtonMail/crypto v0.0.0-20200420072808-71bec3603bf3/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/brunnre8/go.notmuch v0.0.0-20201126061756-caa2daf7093c h1:dh58QrW3/S/aCnQPFoeRRE9zMauKooDFd5zh1dLtxXs=
github.com/brunnre8/go.notmuch v0.0.0-20201126061756-caa2daf7093c/go.mod h1:zJtFvR3NinVdmBiLyB4MyXKmqyVfZEb2cK97ISfTgV8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -64,17 +67,14 @@ github.com/emersion/go-maildir v0.2.0/go.mod h1:I2j27lND/SRLgxROe50Vam81MSaqPFvJ
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
github.com/emersion/go-pgpmail v0.1.0 h1:+NuU9UtGnmmKvmI8evxmTAPXfUod6Gbf2uYT7rxTZ7w=
github.com/emersion/go-pgpmail v0.1.0/go.mod h1:9Sy6uI+dlTN56tcWMtBQHqNDeea27xYItaiZ/3XC76g=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
github.com/emersion/go-pgpmail v0.2.0 h1:BU9kEGQcDVXi6n0v3JBsWAikyo63xsUGZ1lnVaWa6ks=
github.com/emersion/go-pgpmail v0.2.0/go.mod h1:8mQ8Rpn+w28DDaiP8HvJuZjSAymaWr87K3zA/bwwkU0=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac h1:tn/OQ2PmwQ0XFVgAHfjlLyqMewry25Rz7jWnVoh4Ggs=
github.com/emersion/go-sasl v0.0.0-20211008083017-0b9dcfb154ac/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
@@ -108,7 +108,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -161,11 +160,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyoh86/xdg v1.2.0 h1:CERuT/ShdTDj+A2UaX3hQ3mOV369+Sj+wyn2nIRIIkI=
github.com/kyoh86/xdg v1.2.0/go.mod h1:/mg8zwu1+qe76oTFUBnyS7rJzk7LLC0VGEzJyJ19DHs=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -191,7 +188,6 @@ github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab h1:ZjX6I48eZSFetP
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -251,7 +247,6 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -267,7 +262,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
@@ -296,7 +290,6 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -312,7 +305,6 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -392,7 +384,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@@ -449,11 +440,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/lib/keystore.go b/lib/keystore.go
index dcdbd74..df048f4 100644
--- a/lib/keystore.go
+++ b/lib/keystore.go
@@ -5,9 +5,9 @@ import (
	"os"
	"path"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/ProtonMail/go-crypto/openpgp/packet"
	"github.com/kyoh86/xdg"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/packet"
)

var (
diff --git a/lib/messageview.go b/lib/messageview.go
index 532d2c8..8db7994 100644
--- a/lib/messageview.go
+++ b/lib/messageview.go
@@ -5,10 +5,10 @@ import (
	"io"
	"io/ioutil"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message"
	_ "github.com/emersion/go-message/charset"
	"github.com/emersion/go-pgpmail"
	"golang.org/x/crypto/openpgp"

	"git.sr.ht/~rjarry/aerc/models"
	"git.sr.ht/~rjarry/aerc/worker/lib"
diff --git a/widgets/aerc.go b/widgets/aerc.go
index b84dd87..3c52f7e 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -9,10 +9,10 @@ import (
	"strings"
	"time"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/google/shlex"
	"golang.org/x/crypto/openpgp"

	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib"
diff --git a/widgets/compose.go b/widgets/compose.go
index d3e57c5..5ca0932 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -15,6 +15,7 @@ import (
	"strings"
	"time"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/emersion/go-message/mail"
	"github.com/gdamore/tcell/v2"
	"github.com/mattn/go-runewidth"
diff --git a/widgets/pgpinfo.go b/widgets/pgpinfo.go
index febf29a..6c07ed9 100644
--- a/widgets/pgpinfo.go
+++ b/widgets/pgpinfo.go
@@ -6,8 +6,8 @@ import (
	"git.sr.ht/~rjarry/aerc/config"
	"git.sr.ht/~rjarry/aerc/lib/ui"

	"golang.org/x/crypto/openpgp"
	pgperrors "golang.org/x/crypto/openpgp/errors"
	"github.com/ProtonMail/go-crypto/openpgp"
	pgperrors "github.com/ProtonMail/go-crypto/openpgp/errors"
)

type PGPInfo struct {
-- 
2.34.1

[PATCH aerc v2 2/3] pgp: PGP/MIME signing for outgoing emails

Details
Message ID
<20211230092509.211489-2-koni.marti@gmail.com>
In-Reply-To
<20211230092509.211489-1-koni.marti@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +154 -17
implements PGP/MIME signing with go-pgpmail. The Sign() function of
go-pgpmail requires a private (signing) key. The signing key which matches
the senders email address (from field in email header) is looked up
in aerc's copy of the keyring.

Private keys can be exported from gpg into aerc as follows:
$ gpg --export-secret-keys  >> ~/.local/share/aerc/keyring.asc

A message is signed with the ":sign" command. The sign command sets
a bool flag in the Composer struct. Using the command repeatedly will
toggle the flag.

References: https://todo.sr.ht/~rjarry/aerc/6
Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
 commands/compose/sign.go |  44 +++++++++++++++
 lib/keystore.go          |  14 +++++
 widgets/compose.go       | 113 +++++++++++++++++++++++++++++++++------
 3 files changed, 154 insertions(+), 17 deletions(-)
 create mode 100644 commands/compose/sign.go

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
}
-- 
2.34.1

[PATCH aerc v2 3/3] pgp: PGP/MIME encryption for outgoing emails

Details
Message ID
<20211230092509.211489-3-koni.marti@gmail.com>
In-Reply-To
<20211230092509.211489-1-koni.marti@gmail.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +129 -9
implements PGP/MIME encryption with go-pgpmail. The Encrypt() function of
go-pgpmail requires a list of public keys which are taken from the
keystore. The keystore is searched for the email addresses of all
recipients (to, cc, and bcc).
If you want to be able to read the encrypted email afterwards, add
yourself as a recipient in either to, cc, or bcc as well.

Public keys can be exported from gpg into aerc as follows:
$ gpg --export  >> ~/.local/share/aerc/keyring.asc

When composing a message, the encryption is enabled with the
":encrypt" command. This sets a bool flag in the Composer struct.
A reapted application of this command will toggle the flag.
The encrypted message can also be signed by using the ":sign"
command before or after ":encrypt".

References: https://todo.sr.ht/~rjarry/aerc/6
Signed-off-by: Koni Marti <koni.marti@gmail.com>
---
 commands/compose/encrypt.go | 44 +++++++++++++++++++
 lib/keystore.go             | 10 +++++
 widgets/compose.go          | 84 +++++++++++++++++++++++++++++++++----
 3 files changed, 129 insertions(+), 9 deletions(-)
 create mode 100644 commands/compose/encrypt.go

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)
-- 
2.34.1

Re: [PATCH aerc v2 2/3] pgp: PGP/MIME signing for outgoing emails

Details
Message ID
<CGZAT2OSL1DF.NWIOAXBHUNTZ@diabtop>
In-Reply-To
<20211230092509.211489-2-koni.marti@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Koni Marti, Dec 30, 2021 at 10:25:
> diff --git a/widgets/compose.go b/widgets/compose.go
> index 5ca0932..6b7f5cd 100644
> --- a/widgets/compose.go
> +++ b/widgets/compose.go
[snip]
> +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()
> +	}

I have my pgp key on a smartcard so I cannot test this easily. Is the
signature inserted inline in the body or as an attachment?

Re: [PATCH aerc v2 3/3] pgp: PGP/MIME encryption for outgoing emails

Details
Message ID
<CGZAVICRDHT3.2GONDIJGKZAGU@diabtop>
In-Reply-To
<20211230092509.211489-3-koni.marti@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Koni Marti, Dec 30, 2021 at 10:25:
> diff --git a/widgets/compose.go b/widgets/compose.go
> index 6b7f5cd..46d4025 100644
> --- a/widgets/compose.go
> +++ b/widgets/compose.go
[snip]
> +	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)

Can you describe how does sign+encrypt work?

Re: [PATCH aerc v2 3/3] pgp: PGP/MIME encryption for outgoing emails

Details
Message ID
<CGZCZTDIF6CH.1I41YTYN90PJ5@kfn52>
In-Reply-To
<CGZAVICRDHT3.2GONDIJGKZAGU@diabtop> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
>
> Can you describe how does sign+encrypt work?

Sure. We are looking at four cases: 1) no signing/no encryption, 
2) only signing, 3) only encryption, 4) signing+encryption. 

Case 1: we just use the current code, i.e. call writeMsgImpl with the 
WriteMessage arguments.

Case 2: we need a signing key (i.e. a private key). For this we search the 
keystore for the sender's private key. Then we call the Sign function from the 
go-pgpmail package which provides us with a new io.WriterCloser that we use 
for calling the writeMsgImpl function.

Case 3: we only need the public keys of your recipients (so that they can 
decrypt the message with their private keys). For this we need to loop over all 
recipients in the to, cc, and bcc fields and fetch their public keys from the 
keystore. Then, we call the Encrypt function from go-pgpmail (Setting the 
signer parameter to nil) and call writeMsgImpl.

Case 4: Combination of cases 2 and 3. We call the Encrypt function of go-pgpmail 
with a valid signer.


With a smartcard you might be able to test the encrypt function since it 
requires only your public key. If your public key is in the aerc keystore, you 
could send an encrypted message to yourself.
You will not be able to read the message in aerc though (since there is no 
private key in the keystore), but you can pipe it to gpg or an equivalent 
program to decrypt it with your private key on the smartcard. I tested the 
piping with ":pipe gpg -d" and it works in my case.

Re: [PATCH aerc v2 2/3] pgp: PGP/MIME signing for outgoing emails

Details
Message ID
<CGZD2JA0XGZB.19PIC70SNEZ6P@kfn52>
In-Reply-To
<CGZAT2OSL1DF.NWIOAXBHUNTZ@diabtop> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
>
> I have my pgp key on a smartcard so I cannot test this easily. Is the
> signature inserted inline in the body or as an attachment?

The signature is inserted according to RFC3156 (https://datatracker.ietf.org/doc/html/rfc3156) 
as mime (content-type: application/pgp-signature) within a content-type: multipart/signed.

The function "writeMsgImpl" has no information/knowledge about the signing
process. The WriteMessage function is the wrapper for writeMsgImpl and
provides a different io.WriterCloser when you want to sign your message. This
also ensures that attachments are properly signed. Basically, I introduced
an abstraction layer: the "old" WriteMessage code has been shifted into
writeMsgImpl and wrapped with the pgp capability in WriteMessage.

Re: [PATCH aerc v2 1/3] pgp: update openpgp packages (go-crypto and go-pgpmail)

Details
Message ID
<CGZGIF6CAQWS.2MWKEAR73FRAS@diabtop>
In-Reply-To
<20211230092509.211489-1-koni.marti@gmail.com> (view parent)
DKIM signature
missing
Download raw message
Koni Marti, Dec 30, 2021 at 10:25:
> Replaces golang.org/x/crypto with github.com/ProtonMail/go-crypto
> consistently and updates go-pgpmail to v0.2.0
>
> Signed-off-by: Koni Marti <koni.marti@gmail.com>

Applied on master. Thanks!
Reply to thread Export thread (mbox)