aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

commit 3139148c7b9ad6ed4fb9cd8cd3e4160a9b9ee46f
parent a21afdaa6bab8f6d05bbe9272700eef571548a59
Author: Drew DeVault <sir@cmpwn.com>
Date:   Wed, 31 Jan 2018 21:54:52 -0500

Add certificate approval flow

Diffstat:
Mui/account.go | 25++++++++++++++++++-------
Mworker/imap/worker.go | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mworker/types/messages.go | 26++++++++++++++++++--------
3 files changed, 91 insertions(+), 41 deletions(-)

diff --git a/ui/account.go b/ui/account.go @@ -5,8 +5,6 @@ import ( tb "github.com/nsf/termbox-go" - "github.com/davecgh/go-spew/spew" - "git.sr.ht/~sircmpwn/aerc2/config" "git.sr.ht/~sircmpwn/aerc2/worker" "git.sr.ht/~sircmpwn/aerc2/worker/types" @@ -64,12 +62,25 @@ func (acc *AccountTab) GetChannel() chan types.WorkerMessage { return acc.Worker.GetMessages() } +func (acc *AccountTab) postAction(msg types.WorkerMessage) { + acc.logger.Printf("-> %T\n", msg) + acc.Worker.PostAction(msg) +} + func (acc *AccountTab) HandleMessage(msg types.WorkerMessage) { - switch msg.InResponseTo().(type) { - case types.Configure: - // Avoid printing passwords - acc.logger.Printf("<- %T\n", msg) + acc.logger.Printf("<- %T\n", msg) + switch msg.(type) { + case types.Ack: + // no-op + case types.ApproveCertificate: + // TODO: Ask the user + acc.logger.Println("Approving certificate") + acc.postAction(types.Ack{ + Message: types.RespondTo(msg), + }) default: - acc.logger.Printf("<- %s", spew.Sdump(msg)) + acc.postAction(types.Unsupported{ + Message: types.RespondTo(msg), + }) } } diff --git a/worker/imap/worker.go b/worker/imap/worker.go @@ -1,12 +1,13 @@ package imap import ( + "crypto/tls" + "crypto/x509" "fmt" "log" "net/url" "strings" - "github.com/davecgh/go-spew/spew" "github.com/emersion/go-imap" "github.com/emersion/go-imap-idle" "github.com/emersion/go-imap/client" @@ -54,6 +55,45 @@ func (w *IMAPWorker) PostAction(msg types.WorkerMessage) { w.actions <- msg } +func (w *IMAPWorker) postMessage(msg types.WorkerMessage) { + w.logger.Printf("=> %T\n", msg) + w.messages <- msg +} + +func (w *IMAPWorker) verifyPeerCert(msg types.WorkerMessage) func( + rawCerts [][]byte, _ [][]*x509.Certificate) error { + + return func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + pool := x509.NewCertPool() + for _, rawCert := range rawCerts { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return err + } + pool.AddCert(cert) + } + + request := types.ApproveCertificate{ + Message: types.RespondTo(msg), + CertPool: pool, + } + w.postMessage(request) + + response := <-w.actions + if response.InResponseTo() != request { + return fmt.Errorf("Expected UI to answer cert request") + } + switch response.(type) { + case types.Ack: + return nil + case types.Disconnect: + return fmt.Errorf("UI rejected certificate") + default: + return fmt.Errorf("Expected UI to answer cert request") + } + } +} + func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { switch msg := msg.(type) { case types.Ping: @@ -78,12 +118,14 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { w.config.scheme = u.Scheme w.config.user = u.User case types.Connect: - // TODO: populate TLS config - var ( c *client.Client err error ) + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + VerifyPeerCertificate: w.verifyPeerCert(&msg), + } switch w.config.scheme { case "imap": c, err = client.Dial(w.config.addr) @@ -92,12 +134,12 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { } if !w.config.insecure { - if err := c.StartTLS(nil); err != nil { + if err := c.StartTLS(tlsConfig); err != nil { return err } } case "imaps": - c, err = client.DialTLS(w.config.addr, nil) + c, err = client.DialTLS(w.config.addr, tlsConfig) if err != nil { return err } @@ -131,40 +173,27 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { return nil } -// Logs an action but censors passwords -func (w *IMAPWorker) logAction(msg types.WorkerMessage) { - switch msg := msg.(type) { - case types.Configure: - src := msg.Config.Source - msg.Config.Source = "[obsfucated]" - w.logger.Printf("<= %s", spew.Sdump(msg)) - msg.Config.Source = src - default: - w.logger.Printf("<= %s", spew.Sdump(msg)) - } -} - func (w *IMAPWorker) Run() { for { select { case msg := <-w.actions: - w.logAction(msg) + w.logger.Printf("<= %T\n", msg) if err := w.handleMessage(msg); err == errUnsupported { - w.messages <- types.Unsupported{ + w.postMessage(types.Unsupported{ Message: types.RespondTo(msg), - } + }) } else if err != nil { - w.messages <- types.Error{ + w.postMessage(types.Error{ Message: types.RespondTo(msg), Error: err, - } + }) } else { - w.messages <- types.Ack{ + w.postMessage(types.Ack{ Message: types.RespondTo(msg), - } + }) } case update := <-w.updates: - w.logger.Printf("[= %s", spew.Sdump(update)) + w.logger.Printf("[= %T", update) } } } diff --git a/worker/types/messages.go b/worker/types/messages.go @@ -1,6 +1,8 @@ package types import ( + "crypto/x509" + "git.sr.ht/~sircmpwn/aerc2/config" ) @@ -12,6 +14,16 @@ type Message struct { inResponseTo WorkerMessage } +func RespondTo(msg WorkerMessage) Message { + return Message{ + inResponseTo: msg, + } +} + +func (m Message) InResponseTo() WorkerMessage { + return m.inResponseTo +} + // Meta-messages type Ack struct { @@ -27,7 +39,7 @@ type Unsupported struct { Message } -// Commands +// Actions type Ping struct { Message @@ -46,12 +58,10 @@ type Disconnect struct { Message } -func RespondTo(msg WorkerMessage) Message { - return Message{ - inResponseTo: msg, - } -} +// Messages -func (m Message) InResponseTo() WorkerMessage { - return m.inResponseTo +// Respond with an Ack to approve or Disconnect to reject +type ApproveCertificate struct { + Message + CertPool *x509.CertPool }