mirror of
https://github.com/mjl-/mox.git
synced 2025-07-15 10: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:
@ -113,7 +113,7 @@ func FuzzServer(f *testing.F) {
|
||||
const viaHTTPS = false
|
||||
err := serverConn.SetDeadline(time.Now().Add(time.Second))
|
||||
flog(err, "set server deadline")
|
||||
serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, 100<<10, false, false, false, nil, 0)
|
||||
serve("test", cid, dns.Domain{ASCII: "mox.example"}, nil, serverConn, resolver, submission, false, viaHTTPS, false, 100<<10, false, false, false, nil, 0)
|
||||
cid++
|
||||
}
|
||||
|
||||
|
@ -206,12 +206,14 @@ func Listen() {
|
||||
listener := mox.Conf.Static.Listeners[name]
|
||||
|
||||
var tlsConfig, tlsConfigDelivery *tls.Config
|
||||
var noTLSClientAuth bool
|
||||
if listener.TLS != nil {
|
||||
tlsConfig = listener.TLS.Config
|
||||
// For SMTP delivery, if we get a TLS handshake for an SNI hostname that we don't
|
||||
// allow, we'll fallback to a certificate for the listener hostname instead of
|
||||
// causing the connection to fail. May improve interoperability.
|
||||
tlsConfigDelivery = listener.TLS.ConfigFallback
|
||||
noTLSClientAuth = listener.TLS.ClientAuthDisabled
|
||||
}
|
||||
|
||||
maxMsgSize := listener.SMTPMaxMessageSize
|
||||
@ -234,7 +236,7 @@ func Listen() {
|
||||
// https://github.com/golang/go/issues/70232.
|
||||
tlsConfigDelivery.SessionTicketsDisabled = listener.SMTP.TLSSessionTicketsDisabled == nil || *listener.SMTP.TLSSessionTicketsDisabled
|
||||
}
|
||||
listen1("smtp", name, ip, port, hostname, tlsConfigDelivery, false, false, maxMsgSize, false, listener.SMTP.RequireSTARTTLS, !listener.SMTP.NoRequireTLS, listener.SMTP.DNSBLZones, firstTimeSenderDelay)
|
||||
listen1("smtp", name, ip, port, hostname, tlsConfigDelivery, false, false, noTLSClientAuth, maxMsgSize, false, listener.SMTP.RequireSTARTTLS, !listener.SMTP.NoRequireTLS, listener.SMTP.DNSBLZones, firstTimeSenderDelay)
|
||||
}
|
||||
}
|
||||
if listener.Submission.Enabled {
|
||||
@ -244,7 +246,7 @@ func Listen() {
|
||||
}
|
||||
port := config.Port(listener.Submission.Port, 587)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("submission", name, ip, port, hostname, tlsConfig, true, false, maxMsgSize, !listener.Submission.NoRequireSTARTTLS, !listener.Submission.NoRequireSTARTTLS, true, nil, 0)
|
||||
listen1("submission", name, ip, port, hostname, tlsConfig, true, false, noTLSClientAuth, maxMsgSize, !listener.Submission.NoRequireSTARTTLS, !listener.Submission.NoRequireSTARTTLS, true, nil, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,7 +257,7 @@ func Listen() {
|
||||
}
|
||||
port := config.Port(listener.Submissions.Port, 465)
|
||||
for _, ip := range listener.IPs {
|
||||
listen1("submissions", name, ip, port, hostname, tlsConfig, true, true, maxMsgSize, true, true, true, nil, 0)
|
||||
listen1("submissions", name, ip, port, hostname, tlsConfig, true, true, noTLSClientAuth, maxMsgSize, true, true, true, nil, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,7 +265,7 @@ func Listen() {
|
||||
|
||||
var servers []func()
|
||||
|
||||
func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig *tls.Config, submission, xtls bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig *tls.Config, submission, xtls, noTLSClientAuth bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
log := mlog.New("smtpserver", nil)
|
||||
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||
if os.Getuid() == 0 {
|
||||
@ -297,7 +299,7 @@ func listen1(protocol, name, ip string, port int, hostname dns.Domain, tlsConfig
|
||||
|
||||
// Package is set on the resolver by the dkim/spf/dmarc/etc packages.
|
||||
resolver := dns.StrictResolver{Log: log.Logger}
|
||||
go serve(name, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, xtls, false, maxMessageSize, requireTLSForAuth, requireTLSForDelivery, requireTLS, dnsBLs, firstTimeSenderDelay)
|
||||
go serve(name, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, xtls, false, noTLSClientAuth, maxMessageSize, requireTLSForAuth, requireTLSForDelivery, requireTLS, dnsBLs, firstTimeSenderDelay)
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,10 +323,11 @@ type conn struct {
|
||||
origConn net.Conn
|
||||
conn net.Conn
|
||||
|
||||
tls bool
|
||||
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
|
||||
viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN).
|
||||
resolver dns.Resolver
|
||||
tls bool
|
||||
extRequireTLS bool // Whether to announce and allow the REQUIRETLS extension.
|
||||
viaHTTPS bool // Whether the connection came in via the HTTPS port (using TLS ALPN).
|
||||
noTLSClientAuth bool
|
||||
resolver dns.Resolver
|
||||
// The "x" in the readers and writes indicate Read and Write errors use panic to
|
||||
// propagate the error.
|
||||
xbr *bufio.Reader
|
||||
@ -435,7 +438,7 @@ func (c *conn) loginAttempt(useTLS bool, authMech string) store.LoginAttempt {
|
||||
// makeTLSConfig makes a new tls config that is bound to the connection for
|
||||
// possible client certificate authentication in case of submission.
|
||||
func (c *conn) makeTLSConfig() *tls.Config {
|
||||
if !c.submission {
|
||||
if !c.submission || c.noTLSClientAuth {
|
||||
return c.baseTLSConfig
|
||||
}
|
||||
|
||||
@ -540,7 +543,7 @@ func (c *conn) tlsClientAuthVerifyPeerCertParsed(cert *x509.Certificate) error {
|
||||
if err == bstore.ErrAbsent {
|
||||
la.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)
|
||||
}
|
||||
la.LoginAddress = pubKey.LoginAddress
|
||||
|
||||
@ -620,7 +623,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 {
|
||||
@ -634,6 +637,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 {
|
||||
@ -860,10 +864,10 @@ var cleanClose struct{} // Sentinel value for panic/recover indicating clean clo
|
||||
func ServeTLSConn(listenerName string, hostname dns.Domain, conn *tls.Conn, tlsConfig *tls.Config, submission, viaHTTPS bool, maxMsgSize int64, requireTLS bool) {
|
||||
log := mlog.New("smtpserver", nil)
|
||||
resolver := dns.StrictResolver{Log: log.Logger}
|
||||
serve(listenerName, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, true, viaHTTPS, maxMsgSize, true, true, requireTLS, nil, 0)
|
||||
serve(listenerName, mox.Cid(), hostname, tlsConfig, conn, resolver, submission, true, viaHTTPS, true, maxMsgSize, true, true, requireTLS, nil, 0)
|
||||
}
|
||||
|
||||
func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.Config, nc net.Conn, resolver dns.Resolver, submission, xtls, viaHTTPS bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.Config, nc net.Conn, resolver dns.Resolver, submission, xtls, viaHTTPS, noTLSClientAuth bool, maxMessageSize int64, requireTLSForAuth, requireTLSForDelivery, requireTLS bool, dnsBLs []dns.Domain, firstTimeSenderDelay time.Duration) {
|
||||
var localIP, remoteIP net.IP
|
||||
if a, ok := nc.LocalAddr().(*net.TCPAddr); ok {
|
||||
localIP = a.IP
|
||||
@ -890,6 +894,7 @@ func serve(listenerName string, cid int64, hostname dns.Domain, tlsConfig *tls.C
|
||||
submission: submission,
|
||||
tls: xtls,
|
||||
viaHTTPS: viaHTTPS,
|
||||
noTLSClientAuth: noTLSClientAuth,
|
||||
extRequireTLS: requireTLS,
|
||||
resolver: resolver,
|
||||
lastlog: time.Now(),
|
||||
@ -1209,7 +1214,7 @@ func (c *conn) cmdHello(p *parser, ehlo bool) {
|
||||
// case, or it would trigger the mechanism downgrade detection.
|
||||
mechs = "SCRAM-SHA-256-PLUS SCRAM-SHA-256 SCRAM-SHA-1-PLUS SCRAM-SHA-1 CRAM-MD5 PLAIN LOGIN"
|
||||
}
|
||||
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 {
|
||||
mechs = "EXTERNAL " + mechs
|
||||
}
|
||||
c.xbwritelinef("250-AUTH %s", mechs)
|
||||
|
@ -257,7 +257,7 @@ func (ts *testserver) runRaw(fn func(clientConn net.Conn)) {
|
||||
defer func() { <-serverdone }()
|
||||
|
||||
go func() {
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, ts.serverConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, 100<<20, false, false, ts.requiretls, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, ts.serverConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, false, 100<<20, false, false, ts.requiretls, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
@ -476,7 +476,7 @@ func TestSubmission(t *testing.T) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{fakeCert(ts.t, false)},
|
||||
}
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, ts.immediateTLS, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
@ -1426,7 +1426,7 @@ func TestNonSMTP(t *testing.T) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{fakeCert(ts.t, false)},
|
||||
}
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
serve("test", ts.cid-2, dns.Domain{ASCII: "mox.example"}, tlsConfig, serverConn, ts.resolver, ts.submission, false, false, false, 100<<20, false, false, false, ts.dnsbls, 0)
|
||||
close(serverdone)
|
||||
}()
|
||||
|
||||
|
Reference in New Issue
Block a user