Add support for TLS listeners

Support for -tls-cert and -tls-key to point to a certificate and
key to encrypt communications between the user and hydroxide.

Support for -tls-client-ca to ask client to present a certificate
signed by the provided CA.

Closes: https://github.com/emersion/hydroxide/issues/13
This commit is contained in:
primalmotion 2021-02-24 00:48:29 -08:00 committed by GitHub
parent b812444ff8
commit eaed359829
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 14 deletions

View File

@ -3,6 +3,7 @@ package main
import (
"bufio"
"bytes"
"crypto/tls"
"flag"
"fmt"
"io"
@ -21,6 +22,7 @@ import (
"github.com/emersion/hydroxide/auth"
"github.com/emersion/hydroxide/carddav"
"github.com/emersion/hydroxide/config"
"github.com/emersion/hydroxide/events"
"github.com/emersion/hydroxide/exports"
imapbackend "github.com/emersion/hydroxide/imap"
@ -39,25 +41,32 @@ func newClient() *protonmail.Client {
}
}
func listenAndServeSMTP(addr string, debug bool, authManager *auth.Manager) error {
func listenAndServeSMTP(addr string, debug bool, authManager *auth.Manager, tlsConfig *tls.Config) error {
be := smtpbackend.New(authManager)
s := smtp.NewServer(be)
s.Addr = addr
s.Domain = "localhost" // TODO: make this configurable
s.AllowInsecureAuth = true // TODO: remove this
s.AllowInsecureAuth = tlsConfig == nil
s.TLSConfig = tlsConfig
if debug {
s.Debug = os.Stdout
}
if s.TLSConfig != nil {
log.Println("SMTP server listening with TLS on", s.Addr)
return s.ListenAndServeTLS()
}
log.Println("SMTP server listening on", s.Addr)
return s.ListenAndServe()
}
func listenAndServeIMAP(addr string, debug bool, authManager *auth.Manager, eventsManager *events.Manager) error {
func listenAndServeIMAP(addr string, debug bool, authManager *auth.Manager, eventsManager *events.Manager, tlsConfig *tls.Config) error {
be := imapbackend.New(authManager, eventsManager)
s := imapserver.New(be)
s.Addr = addr
s.AllowInsecureAuth = true // TODO: remove this
s.AllowInsecureAuth = tlsConfig == nil
s.TLSConfig = tlsConfig
if debug {
s.Debug = os.Stdout
}
@ -65,15 +74,21 @@ func listenAndServeIMAP(addr string, debug bool, authManager *auth.Manager, even
s.Enable(imapspacialuse.NewExtension())
s.Enable(imapmove.NewExtension())
if s.TLSConfig != nil {
log.Println("IMAP server listening with TLS on", s.Addr)
return s.ListenAndServeTLS()
}
log.Println("IMAP server listening on", s.Addr)
return s.ListenAndServe()
}
func listenAndServeCardDAV(addr string, authManager *auth.Manager, eventsManager *events.Manager) error {
func listenAndServeCardDAV(addr string, authManager *auth.Manager, eventsManager *events.Manager, tlsConfig *tls.Config) error {
handlers := make(map[string]http.Handler)
s := &http.Server{
Addr: addr,
TLSConfig: tlsConfig,
Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("WWW-Authenticate", "Basic")
@ -108,6 +123,11 @@ func listenAndServeCardDAV(addr string, authManager *auth.Manager, eventsManager
}),
}
if s.TLSConfig != nil {
log.Println("CardDAV server listening with TLS on", s.Addr)
return s.ListenAndServeTLS("", "")
}
log.Println("CardDAV server listening on", s.Addr)
return s.ListenAndServe()
}
@ -147,7 +167,13 @@ Global options:
-imap-port example.com
IMAP port on which hydroxide listens, defaults to 1143
-carddav-port example.com
CardDAV port on which hydroxide listens, defaults to 8080`
CardDAV port on which hydroxide listens, defaults to 8080
-tls-cert /path/to/cert.pem
Path to the certificate to use for incoming connections (Optional)
-tls-key /path/to/key.pem
Path to the certificate key to use for incoming connections (Optional)
-tls-client-ca /path/to/ca.pem
If set, clients must provide a certificate signed by the given CA (Optional)`
func main() {
flag.BoolVar(&debug, "debug", false, "Enable debug logs")
@ -161,6 +187,10 @@ func main() {
carddavHost := flag.String("carddav-host", "127.0.0.1", "Allowed CardDAV email hostname on which hydroxide listens, defaults to 127.0.0.1")
carddavPort := flag.String("carddav-port", "8080", "CardDAV port on which hydroxide listens, defaults to 8080")
tlsCert := flag.String("tls-cert", "", "Path to the certificate to use for incoming connections")
tlsCertKey := flag.String("tls-key", "", "Path to the certificate key to use for incoming connections")
tlsClientCA := flag.String("tls-client-ca", "", "If set, clients must provide a certificate signed by the given CA")
authCmd := flag.NewFlagSet("auth", flag.ExitOnError)
exportSecretKeysCmd := flag.NewFlagSet("export-secret-keys", flag.ExitOnError)
importMessagesCmd := flag.NewFlagSet("import-messages", flag.ExitOnError)
@ -172,6 +202,11 @@ func main() {
flag.Parse()
tlsConfig, err := config.TLS(*tlsCert, *tlsCertKey, *tlsClientCA)
if err != nil {
log.Fatal(err)
}
cmd := flag.Arg(0)
switch cmd {
case "auth":
@ -412,17 +447,17 @@ func main() {
case "smtp":
addr := *smtpHost + ":" + *smtpPort
authManager := auth.NewManager(newClient)
log.Fatal(listenAndServeSMTP(addr, debug, authManager))
log.Fatal(listenAndServeSMTP(addr, debug, authManager, tlsConfig))
case "imap":
addr := *imapHost + ":" + *imapPort
authManager := auth.NewManager(newClient)
eventsManager := events.NewManager()
log.Fatal(listenAndServeIMAP(addr, debug, authManager, eventsManager))
log.Fatal(listenAndServeIMAP(addr, debug, authManager, eventsManager, tlsConfig))
case "carddav":
addr := *carddavHost + ":" + *carddavPort
authManager := auth.NewManager(newClient)
eventsManager := events.NewManager()
log.Fatal(listenAndServeCardDAV(addr, authManager, eventsManager))
log.Fatal(listenAndServeCardDAV(addr, authManager, eventsManager, tlsConfig))
case "serve":
smtpAddr := *smtpHost + ":" + *smtpPort
imapAddr := *imapHost + ":" + *imapPort
@ -433,13 +468,13 @@ func main() {
done := make(chan error, 3)
go func() {
done <- listenAndServeSMTP(smtpAddr, debug, authManager)
done <- listenAndServeSMTP(smtpAddr, debug, authManager, tlsConfig)
}()
go func() {
done <- listenAndServeIMAP(imapAddr, debug, authManager, eventsManager)
done <- listenAndServeIMAP(imapAddr, debug, authManager, eventsManager, tlsConfig)
}()
go func() {
done <- listenAndServeCardDAV(carddavAddr, authManager, eventsManager)
done <- listenAndServeCardDAV(carddavAddr, authManager, eventsManager, tlsConfig)
}()
log.Fatal(<-done)
default:

36
config/tls.go Normal file
View File

@ -0,0 +1,36 @@
package config
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
)
func TLS(certPath string, keyPath string, clientCAPath string) (*tls.Config, error) {
tlsConfig := &tls.Config{}
if certPath != "" && keyPath != "" {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("error: unable load key pair: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if clientCAPath != "" {
data, err := ioutil.ReadFile(clientCAPath)
if err != nil {
return nil, fmt.Errorf("error: unable read CA file: %s", err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(data)
tlsConfig.ClientCAs = pool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
return tlsConfig, nil
}