~sircmpwn/aerc

Add support for pinning TLS certificate v1 PROPOSED

Niko Böckerman: 1
 Add support for pinning TLS certificate

 3 files changed, 74 insertions(+), 23 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/aerc/patches/20445/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Add support for pinning TLS certificate Export this patch

Allow specifying certificate file through account configuration for
source and outgoing connections.
---
 commands/compose/send.go | 13 ++++---
 config/config.go         | 75 +++++++++++++++++++++++++++++++---------
 worker/imap/worker.go    |  9 +++--
 3 files changed, 74 insertions(+), 23 deletions(-)

diff --git a/commands/compose/send.go b/commands/compose/send.go
index f61478f..31cfbdc 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -3,6 +3,7 @@ package compose
import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"net/url"
@@ -86,6 +87,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
		scheme:   scheme,
		auth:     auth,
		starttls: starttls,
		certPool: config.OutgoingCertPool,
		from:     from,
		rcpts:    rcpts,
	}
@@ -176,6 +178,7 @@ type sendCtx struct {
	scheme   string
	auth     string
	starttls bool
	certPool *x509.CertPool
	from     *mail.Address
	rcpts    []*mail.Address
}
@@ -313,9 +316,9 @@ func newSmtpSender(ctx sendCtx) (io.WriteCloser, error) {
	)
	switch ctx.scheme {
	case "smtp":
		conn, err = connectSmtp(ctx.starttls, ctx.uri.Host)
		conn, err = connectSmtp(ctx.starttls, ctx.uri.Host, ctx.certPool)
	case "smtps":
		conn, err = connectSmtps(ctx.uri.Host)
		conn, err = connectSmtps(ctx.uri.Host, ctx.certPool)
	default:
		return nil, fmt.Errorf("not an smtp protocol %s", ctx.scheme)
	}
@@ -353,7 +356,7 @@ func newSmtpSender(ctx sendCtx) (io.WriteCloser, error) {
	return s.w, nil
}

func connectSmtp(starttls bool, host string) (*smtp.Client, error) {
func connectSmtp(starttls bool, host string, certPool *x509.CertPool) (*smtp.Client, error) {
	serverName := host
	if !strings.ContainsRune(host, ':') {
		host = host + ":587" // Default to submission port
@@ -374,6 +377,7 @@ func connectSmtp(starttls bool, host string) (*smtp.Client, error) {
		}
		if err = conn.StartTLS(&tls.Config{
			ServerName: serverName,
			RootCAs:    certPool,
		}); err != nil {
			conn.Close()
			return nil, errors.Wrap(err, "StartTLS")
@@ -390,7 +394,7 @@ func connectSmtp(starttls bool, host string) (*smtp.Client, error) {
	return conn, nil
}

func connectSmtps(host string) (*smtp.Client, error) {
func connectSmtps(host string, certPool *x509.CertPool) (*smtp.Client, error) {
	serverName := host
	if !strings.ContainsRune(host, ':') {
		host = host + ":465" // Default to smtps port
@@ -399,6 +403,7 @@ func connectSmtps(host string) (*smtp.Client, error) {
	}
	conn, err := smtp.DialTLS(host, &tls.Config{
		ServerName: serverName,
		RootCAs:    certPool,
	})
	if err != nil {
		return nil, errors.Wrap(err, "smtp.DialTLS")
diff --git a/config/config.go b/config/config.go
index 8b409fe..28fd8a8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,6 +1,7 @@
package config

import (
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
@@ -70,23 +71,25 @@ const (
)

type AccountConfig struct {
	Archive         string
	CopyTo          string
	Default         string
	Postpone        string
	From            string
	Aliases         string
	Name            string
	Source          string
	SourceCredCmd   string
	Folders         []string
	FoldersExclude  []string
	Params          map[string]string
	Outgoing        string
	OutgoingCredCmd string
	SignatureFile   string
	SignatureCmd    string
	FoldersSort     []string `ini:"folders-sort" delim:","`
	Archive          string
	CopyTo           string
	Default          string
	Postpone         string
	From             string
	Aliases          string
	Name             string
	Source           string
	SourceCredCmd    string
	SourceCertPool   *x509.CertPool
	Folders          []string
	FoldersExclude   []string
	Params           map[string]string
	Outgoing         string
	OutgoingCredCmd  string
	OutgoingCertPool *x509.CertPool
	SignatureFile    string
	SignatureCmd     string
	FoldersSort      []string `ini:"folders-sort" delim:","`
}

type BindingConfig struct {
@@ -186,6 +189,11 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
		if err = sec.MapTo(&account); err != nil {
			return nil, err
		}

		var (
			sourceCaFile   string
			outgoingCaFile string
		)
		for key, val := range sec.KeysHash() {
			if key == "folders" {
				folders := strings.Split(val, ",")
@@ -209,6 +217,10 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
				account.CopyTo = val
			} else if key == "archive" {
				account.Archive = val
			} else if key == "source-cafile" {
				sourceCaFile = val
			} else if key == "outgoing-cafile" {
				outgoingCaFile = val
			} else if key != "name" {
				account.Params[key] = val
			}
@@ -232,11 +244,40 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
		}
		account.Outgoing = outgoing

		if sourceCaFile != "" {
			certPool, err := parseCaFile(sourceCaFile)
			if err != nil {
				return nil, fmt.Errorf("Invalid source cafile for %s: %s", _sec, err)
			}
			account.SourceCertPool = certPool
		}

		if outgoingCaFile != "" {
			certPool, err := parseCaFile(outgoingCaFile)
			if err != nil {
				return nil, fmt.Errorf("Invalid outgoing cafile for %s: %s", _sec, err)
			}
			account.OutgoingCertPool = certPool
		}

		accounts = append(accounts, account)
	}
	return accounts, nil
}

func parseCaFile(file string) (*x509.CertPool, error) {
	caCert, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	caCertPool := x509.NewCertPool()
	ok := caCertPool.AppendCertsFromPEM(caCert)
	if !ok {
		return nil, fmt.Errorf("Failed to append cert file %s", file)
	}
	return caCertPool, nil
}

func parseCredential(cred, command string) (string, error) {
	if cred == "" || command == "" {
		return cred, nil
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index dab0afb..4ab4e97 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -2,6 +2,7 @@ package imap

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net/url"
	"strings"
@@ -39,6 +40,7 @@ type IMAPWorker struct {
		user        *url.Userinfo
		folders     []string
		oauthBearer lib.OAuthBearer
		cacertpool  *x509.CertPool
	}

	client   *imapClient
@@ -105,6 +107,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
			w.config.addr += ":" + w.config.scheme
		}

		w.config.cacertpool = msg.Config.SourceCertPool

		w.config.user = u.User
		w.config.folders = msg.Config.Folders
	case *types.Connect:
@@ -112,6 +116,7 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
			c   *client.Client
			err error
		)
		tlsConf := &tls.Config{RootCAs: w.config.cacertpool}
		switch w.config.scheme {
		case "imap":
			c, err = client.Dial(w.config.addr)
@@ -120,12 +125,12 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
			}

			if !w.config.insecure {
				if err := c.StartTLS(&tls.Config{}); err != nil {
				if err := c.StartTLS(tlsConf); err != nil {
					return err
				}
			}
		case "imaps":
			c, err = client.DialTLS(w.config.addr, &tls.Config{})
			c, err = client.DialTLS(w.config.addr, tlsConf)
			if err != nil {
				return err
			}
--
2.26.2