aerc/lib/messageview.go
Koni Marti 698c0957d7 pgp: ensure CRLF line endings in pgpmail reader
Ensure CRLF line endings in the pgpmail reader. Fix the pgp signature
verification for maildir and notmuch.

These backends do not return the full message body with CRLF
line endings. But the accepted OpenPGP convention is for signed data to
end with a <CR><LF> sequence (see RFC3156).

If this is not the case the signed and transmitted data are considered
not the same and thus signature verification fails.

Link: https://datatracker.ietf.org/doc/html/rfc3156

Reported-by: Tim Culverhouse <tim@timculverhouse.com>
Signed-off-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Tim Culverhouse <tim@timculverhouse.com>
2022-04-25 12:31:33 +02:00

137 lines
3.1 KiB
Go

package lib
import (
"bytes"
"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"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/worker/lib"
"git.sr.ht/~rjarry/aerc/worker/types"
)
// This is an abstraction for viewing a message with semi-transparent PGP
// support.
type MessageView interface {
// Returns the MessageInfo for this message
MessageInfo() *models.MessageInfo
// Returns the BodyStructure for this message
BodyStructure() *models.BodyStructure
// Returns the message store that this message was originally sourced from
Store() *MessageStore
// Fetches a specific body part for this message
FetchBodyPart(part []int, cb func(io.Reader))
PGPDetails() *openpgp.MessageDetails
}
func usePGP(info *models.BodyStructure) bool {
if info == nil {
return false
}
if info.MIMEType == "application" {
if info.MIMESubType == "pgp-encrypted" ||
info.MIMESubType == "pgp-signature" {
return true
}
}
for _, part := range info.Parts {
if usePGP(part) {
return true
}
}
return false
}
type MessageStoreView struct {
messageInfo *models.MessageInfo
messageStore *MessageStore
message []byte
details *openpgp.MessageDetails
bodyStructure *models.BodyStructure
}
func NewMessageStoreView(messageInfo *models.MessageInfo,
store *MessageStore, decryptKeys openpgp.PromptFunction,
cb func(MessageView, error)) {
msv := &MessageStoreView{messageInfo, store,
nil, nil, messageInfo.BodyStructure}
if usePGP(messageInfo.BodyStructure) {
store.FetchFull([]uint32{messageInfo.Uid}, func(fm *types.FullMessage) {
reader := lib.NewCRLFReader(fm.Content.Reader)
pgpReader, err := pgpmail.Read(reader, Keyring, decryptKeys, nil)
if err != nil {
cb(nil, err)
return
}
msv.message, err = ioutil.ReadAll(pgpReader.MessageDetails.UnverifiedBody)
if err != nil {
cb(nil, err)
return
}
decrypted, err := message.Read(bytes.NewBuffer(msv.message))
if err != nil {
cb(nil, err)
return
}
bs, err := lib.ParseEntityStructure(decrypted)
if err != nil {
cb(nil, err)
return
}
msv.bodyStructure = bs
msv.details = pgpReader.MessageDetails
cb(msv, nil)
})
} else {
cb(msv, nil)
}
store.Flag([]uint32{messageInfo.Uid}, models.SeenFlag, true, nil)
}
func (msv *MessageStoreView) MessageInfo() *models.MessageInfo {
return msv.messageInfo
}
func (msv *MessageStoreView) BodyStructure() *models.BodyStructure {
return msv.bodyStructure
}
func (msv *MessageStoreView) Store() *MessageStore {
return msv.messageStore
}
func (msv *MessageStoreView) PGPDetails() *openpgp.MessageDetails {
return msv.details
}
func (msv *MessageStoreView) FetchBodyPart(part []int, cb func(io.Reader)) {
if msv.message == nil {
msv.messageStore.FetchBodyPart(msv.messageInfo.Uid, part, cb)
return
}
buf := bytes.NewBuffer(msv.message)
msg, err := message.Read(buf)
if err != nil {
panic(err)
}
reader, err := lib.FetchEntityPartReader(msg, part)
if err != nil {
panic(err)
}
cb(reader)
}