mirror of
https://github.com/mjl-/mox.git
synced 2025-07-15 00:54:35 +03:00
add config option to disable tls client auth during tls handshakes
to work around clients, like the gmail smtp client, that tries to authenticate with a webpki-issued certificate (which we don't know). i tried specifying a list of accepted (subjects of) CA certs during the tls handshake (with just 1 entry, with "xmox.nl" as common name), which clients can use to influence their cert selection. however, the gmail smtp client ignores it, so not a solution for the issue where this was raised. also, specifying a list of accepted certs could cause other clients to not send their client cert anymore, breaking existing setups. i also considered only asking for tls client auth when at least one account has a tls pubkey configured. but decided against it since any account can add one on their own (without system admin interaction), changing behaviour of the system and potentially breaking existing submission/tls configs. we now also print the "subject" and "issuer" of certs when tls client auth fails, should be useful for future debugging. for issue #359
This commit is contained in:
@ -362,7 +362,7 @@ func TestAuthenticateTLSClientCert(t *testing.T) {
|
||||
cid := connCounter
|
||||
go func() {
|
||||
defer serverConn.Close()
|
||||
serve("test", cid, &serverConfig, serverConn, true, false, false, "")
|
||||
serve("test", cid, &serverConfig, serverConn, true, false, false, false, "")
|
||||
close(done)
|
||||
}()
|
||||
|
||||
|
@ -144,7 +144,7 @@ func FuzzServer(f *testing.F) {
|
||||
|
||||
err = serverConn.SetDeadline(time.Now().Add(time.Second))
|
||||
flog(err, "set server deadline")
|
||||
serve("test", cid, nil, serverConn, false, true, false, "")
|
||||
serve("test", cid, nil, serverConn, false, false, true, false, "")
|
||||
cid++
|
||||
}
|
||||
|
||||
|
@ -192,9 +192,10 @@ type conn struct {
|
||||
cid int64
|
||||
state state
|
||||
conn net.Conn
|
||||
connBroken bool // Once broken, we won't flush any more data.
|
||||
tls bool // Whether TLS has been initialized.
|
||||
viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN).
|
||||
connBroken bool // Once broken, we won't flush any more data.
|
||||
tls bool // Whether TLS has been initialized.
|
||||
viaHTTPS bool // Whether this connection came in via HTTPS (using TLS ALPN).
|
||||
noTLSClientAuth bool
|
||||
br *bufio.Reader // From remote, with TLS unwrapped in case of TLS, and possibly wrapping inflate.
|
||||
tr *moxio.TraceReader // Kept to change trace level when reading/writing cmd/auth/data.
|
||||
line chan lineErr // If set, instead of reading from br, a line is read from this channel. For reading a line in IDLE while also waiting for mailbox/account updates.
|
||||
@ -384,21 +385,23 @@ func Listen() {
|
||||
listener := mox.Conf.Static.Listeners[name]
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
var noTLSClientAuth bool
|
||||
if listener.TLS != nil {
|
||||
tlsConfig = listener.TLS.Config
|
||||
noTLSClientAuth = listener.TLS.ClientAuthDisabled
|
||||
}
|
||||
|
||||
if listener.IMAP.Enabled {
|
||||
port := config.Port(listener.IMAP.Port, 143)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("imap", name, ip, port, tlsConfig, false, listener.IMAP.NoRequireSTARTTLS)
|
||||
listen1("imap", name, ip, port, tlsConfig, false, noTLSClientAuth, listener.IMAP.NoRequireSTARTTLS)
|
||||
}
|
||||
}
|
||||
|
||||
if listener.IMAPS.Enabled {
|
||||
port := config.Port(listener.IMAPS.Port, 993)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("imaps", name, ip, port, tlsConfig, true, false)
|
||||
listen1("imaps", name, ip, port, tlsConfig, true, noTLSClientAuth, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -406,7 +409,7 @@ func Listen() {
|
||||
|
||||
var servers []func()
|
||||
|
||||
func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config, xtls, noRequireSTARTTLS bool) {
|
||||
func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config, xtls, noTLSClientAuth, noRequireSTARTTLS bool) {
|
||||
log := mlog.New("imapserver", nil)
|
||||
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||
if os.Getuid() == 0 {
|
||||
@ -439,7 +442,7 @@ func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config,
|
||||
}
|
||||
|
||||
metricIMAPConnection.WithLabelValues(protocol).Inc()
|
||||
go serve(listenerName, mox.Cid(), tlsConfig, conn, xtls, noRequireSTARTTLS, false, "")
|
||||
go serve(listenerName, mox.Cid(), tlsConfig, conn, xtls, noTLSClientAuth, noRequireSTARTTLS, false, "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,11 +451,11 @@ func listen1(protocol, listenerName, ip string, port int, tlsConfig *tls.Config,
|
||||
|
||||
// ServeTLSConn serves IMAP on a TLS connection.
|
||||
func ServeTLSConn(listenerName string, conn *tls.Conn, tlsConfig *tls.Config) {
|
||||
serve(listenerName, mox.Cid(), tlsConfig, conn, true, false, true, "")
|
||||
serve(listenerName, mox.Cid(), tlsConfig, conn, true, true, false, true, "")
|
||||
}
|
||||
|
||||
func ServeConnPreauth(listenerName string, cid int64, conn net.Conn, preauthAddress string) {
|
||||
serve(listenerName, cid, nil, conn, false, true, false, preauthAddress)
|
||||
serve(listenerName, cid, nil, conn, false, true, true, false, preauthAddress)
|
||||
}
|
||||
|
||||
// Serve starts serving on all listeners, launching a goroutine per listener.
|
||||
@ -766,7 +769,7 @@ var cleanClose struct{} // Sentinel value for panic/recover indicating clean clo
|
||||
// preauthenticated.
|
||||
//
|
||||
// The connection is closed before returning.
|
||||
func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, xtls, noRequireSTARTTLS, viaHTTPS bool, preauthAddress string) {
|
||||
func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, xtls, noTLSClientAuth, noRequireSTARTTLS, viaHTTPS bool, preauthAddress string) {
|
||||
var remoteIP net.IP
|
||||
if a, ok := nc.RemoteAddr().(*net.TCPAddr); ok {
|
||||
remoteIP = a.IP
|
||||
@ -780,6 +783,7 @@ func serve(listenerName string, cid int64, tlsConfig *tls.Config, nc net.Conn, x
|
||||
conn: nc,
|
||||
tls: xtls,
|
||||
viaHTTPS: viaHTTPS,
|
||||
noTLSClientAuth: noTLSClientAuth,
|
||||
lastlog: time.Now(),
|
||||
baseTLSConfig: tlsConfig,
|
||||
remoteIP: remoteIP,
|
||||
@ -990,6 +994,10 @@ func (c *conn) makeTLSConfig() *tls.Config {
|
||||
// config, so they can be used for this connection too.
|
||||
tlsConf := c.baseTLSConfig.Clone()
|
||||
|
||||
if c.noTLSClientAuth {
|
||||
return tlsConf
|
||||
}
|
||||
|
||||
// Allow client certificate authentication, for use with the sasl "external"
|
||||
// authentication mechanism.
|
||||
tlsConf.ClientAuth = tls.RequestClientCert
|
||||
@ -1089,7 +1097,7 @@ func (c *conn) tlsClientAuthVerifyPeerCertParsed(cert *x509.Certificate) error {
|
||||
if err == bstore.ErrAbsent {
|
||||
c.loginAttempt.Result = store.AuthBadCredentials
|
||||
}
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s: %v", fp, err)
|
||||
return fmt.Errorf("looking up tls public key with fingerprint %s, subject %q, issuer %q: %v", fp, cert.Subject, cert.Issuer, err)
|
||||
}
|
||||
c.loginAttempt.LoginAddress = pubKey.LoginAddress
|
||||
|
||||
@ -1152,7 +1160,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
cancel()
|
||||
|
||||
cs := tlsConn.ConnectionState()
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 {
|
||||
if cs.DidResume && len(cs.PeerCertificates) > 0 && !c.noTLSClientAuth {
|
||||
// Verify client after session resumption.
|
||||
err := c.tlsClientAuthVerifyPeerCertParsed(cs.PeerCertificates[0])
|
||||
if err != nil {
|
||||
@ -1167,6 +1175,7 @@ func (c *conn) xtlsHandshakeAndAuthenticate(conn net.Conn) {
|
||||
slog.String("ciphersuite", ciphersuite),
|
||||
slog.String("sni", cs.ServerName),
|
||||
slog.Bool("resumed", cs.DidResume),
|
||||
slog.Bool("notlsclientauth", c.noTLSClientAuth),
|
||||
slog.Int("clientcerts", len(cs.PeerCertificates)),
|
||||
}
|
||||
if c.account != nil {
|
||||
@ -2390,7 +2399,7 @@ func (c *conn) capabilities() string {
|
||||
} else {
|
||||
caps += " LOGINDISABLED"
|
||||
}
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS {
|
||||
if c.tls && len(c.conn.(*tls.Conn).ConnectionState().PeerCertificates) > 0 && !c.viaHTTPS && !c.noTLSClientAuth {
|
||||
caps += " AUTH=EXTERNAL"
|
||||
}
|
||||
return caps
|
||||
|
@ -556,7 +556,7 @@ func startArgsMore(t *testing.T, uidonly, first, immediateTLS bool, serverConfig
|
||||
cid := connCounter - 1
|
||||
go func() {
|
||||
const viaHTTPS = false
|
||||
serve("test", cid, serverConfig, serverConn, immediateTLS, allowLoginWithoutTLS, viaHTTPS, "")
|
||||
serve("test", cid, serverConfig, serverConn, immediateTLS, false, allowLoginWithoutTLS, viaHTTPS, "")
|
||||
close(done)
|
||||
}()
|
||||
var tc *testconn
|
||||
|
Reference in New Issue
Block a user