diff options
author | Drew DeVault <sir@cmpwn.com> | 2020-03-03 16:20:07 -0500 |
---|---|---|
committer | Drew DeVault <sir@cmpwn.com> | 2020-03-03 16:49:52 -0500 |
commit | f3158b36f1f210ff54febbe82b571c1379b30c98 (patch) | |
tree | 10cde839c9517609f55b8f1057b1cf84ac592632 /lib | |
parent | 89f1684ea4b5e680db7ff06a54b2d4e78212cd12 (diff) | |
download | aerc-f3158b36f1f210ff54febbe82b571c1379b30c98.tar.gz |
Initial support for PGP decryption & signatures
Diffstat (limited to 'lib')
-rw-r--r-- | lib/keystore.go | 81 | ||||
-rw-r--r-- | lib/messageview.go | 128 | ||||
-rw-r--r-- | lib/ui/interfaces.go | 4 | ||||
-rw-r--r-- | lib/ui/ui.go | 4 |
4 files changed, 217 insertions, 0 deletions
diff --git a/lib/keystore.go b/lib/keystore.go new file mode 100644 index 0000000..dcdbd74 --- /dev/null +++ b/lib/keystore.go @@ -0,0 +1,81 @@ +package lib + +import ( + "io" + "os" + "path" + + "github.com/kyoh86/xdg" + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/packet" +) + +var ( + Keyring openpgp.EntityList + + locked bool +) + +func InitKeyring() { + os.MkdirAll(path.Join(xdg.DataHome(), "aerc"), 0700) + + lockpath := path.Join(xdg.DataHome(), "aerc", "keyring.lock") + lockfile, err := os.OpenFile(lockpath, os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + // TODO: Consider connecting to main process over IPC socket + locked = false + } else { + locked = true + lockfile.Close() + } + + keypath := path.Join(xdg.DataHome(), "aerc", "keyring.asc") + keyfile, err := os.Open(keypath) + if os.IsNotExist(err) { + return + } else if err != nil { + panic(err) + } + defer keyfile.Close() + + Keyring, err = openpgp.ReadKeyRing(keyfile) + if err != nil { + panic(err) + } +} + +func UnlockKeyring() { + if !locked { + return + } + lockpath := path.Join(xdg.DataHome(), "aerc", "keyring.lock") + os.Remove(lockpath) +} + +func ImportKeys(r io.Reader) error { + keys, err := openpgp.ReadKeyRing(r) + if err != nil { + return err + } + Keyring = append(Keyring, keys...) + if locked { + keypath := path.Join(xdg.DataHome(), "aerc", "keyring.asc") + keyfile, err := os.OpenFile(keypath, os.O_CREATE|os.O_APPEND, 0600) + if err != nil { + return err + } + defer keyfile.Close() + + for _, key := range keys { + if key.PrivateKey != nil { + err = key.SerializePrivate(keyfile, &packet.Config{}) + } else { + err = key.Serialize(keyfile) + } + if err != nil { + return err + } + } + } + return nil +} diff --git a/lib/messageview.go b/lib/messageview.go new file mode 100644 index 0000000..be3b90f --- /dev/null +++ b/lib/messageview.go @@ -0,0 +1,128 @@ +package lib + +import ( + "bytes" + "io" + "io/ioutil" + + "github.com/emersion/go-message" + _ "github.com/emersion/go-message/charset" + "github.com/emersion/go-pgpmail" + "golang.org/x/crypto/openpgp" + + "git.sr.ht/~sircmpwn/aerc/models" + "git.sr.ht/~sircmpwn/aerc/worker/lib" +) + +// 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(parent *models.BodyStructure, + part []int, cb func(io.Reader)) + + PGPDetails() *openpgp.MessageDetails +} + +func usePGP(info *models.BodyStructure) bool { + 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)) { + + msv := &MessageStoreView{messageInfo, store, + nil, nil, messageInfo.BodyStructure} + + if usePGP(messageInfo.BodyStructure) { + store.FetchFull([]uint32{messageInfo.Uid}, func(reader io.Reader) { + pgpReader, err := pgpmail.Read(reader, Keyring, decryptKeys, nil) + if err != nil { + panic(err) + } + msv.message, err = ioutil.ReadAll(pgpReader.MessageDetails.UnverifiedBody) + if err != nil { + panic(err) + } + decrypted, err := message.Read(bytes.NewBuffer(msv.message)) + if err != nil { + panic(err) + } + bs, err := lib.ParseEntityStructure(decrypted) + if err != nil { + panic(err) + } + msv.bodyStructure = bs + msv.details = pgpReader.MessageDetails + cb(msv) + }) + } else { + cb(msv) + } +} + +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(parent *models.BodyStructure, + part []int, cb func(io.Reader)) { + + if msv.message == nil { + msv.messageStore.FetchBodyPart(msv.messageInfo.Uid, parent, 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) +} diff --git a/lib/ui/interfaces.go b/lib/ui/interfaces.go index 9e79571..c12bdb7 100644 --- a/lib/ui/interfaces.go +++ b/lib/ui/interfaces.go @@ -16,6 +16,10 @@ type Drawable interface { Invalidate() } +type RootDrawable interface { + Initialize(ui *UI) +} + type Interactive interface { // Returns true if the event was handled by this component Event(event tcell.Event) bool diff --git a/lib/ui/ui.go b/lib/ui/ui.go index 16b176d..9a9ed14 100644 --- a/lib/ui/ui.go +++ b/lib/ui/ui.go @@ -55,6 +55,10 @@ func Initialize(content DrawableInteractiveBeeper) (*UI, error) { content.OnBeep(screen.Beep) content.Focus(true) + if root, ok := content.(RootDrawable); ok { + root.Initialize(&state) + } + return &state, nil } |