Koni Marti: 4 pgp: PGP/MIME encryption and signing pgp: update openpgp packages (go-crypto and go-pgpmail) pgp: PGP/MIME signing for outgoing emails pgp: PGP/MIME encryption for outgoing emails 21 files changed, 556 insertions(+), 95 deletions(-)
Koni Marti, Dec 30, 2021 at 10:25:
Koni Marti, Dec 30, 2021 at 10:25:
Koni Marti, Dec 30, 2021 at 10:25:
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~rjarry/aerc-devel/patches/27588/mbox | git am -3Learn more about email & git
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.My bad. Will fix that.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.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.Thanks!
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
builds.sr.ht <builds@sr.ht>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]: mailto:koni.marti@gmail.com ✓ #654840 SUCCESS aerc/patches/.build.yml https://builds.sr.ht/~rjarry/job/654840
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:
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!
--- 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
Koni Marti, Dec 30, 2021 at 10:25:
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() + }
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?
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
Koni Marti, Dec 30, 2021 at 10:25:
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)
Can you describe how does sign+encrypt work?
+ + 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
Koni Marti, Dec 30, 2021 at 10:25: