summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2020-03-03 16:20:07 -0500
committerDrew DeVault <sir@cmpwn.com>2020-03-03 16:49:52 -0500
commitf3158b36f1f210ff54febbe82b571c1379b30c98 (patch)
tree10cde839c9517609f55b8f1057b1cf84ac592632 /lib
parent89f1684ea4b5e680db7ff06a54b2d4e78212cd12 (diff)
downloadaerc-f3158b36f1f210ff54febbe82b571c1379b30c98.tar.gz
Initial support for PGP decryption & signatures
Diffstat (limited to 'lib')
-rw-r--r--lib/keystore.go81
-rw-r--r--lib/messageview.go128
-rw-r--r--lib/ui/interfaces.go4
-rw-r--r--lib/ui/ui.go4
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
 }