From 8dcd6c8067bbe73d2dd638f600cbd42d480c1d7d Mon Sep 17 00:00:00 2001 From: StrongWind <5987034+StrongWind1@users.noreply.github.com> Date: Fri, 8 May 2026 13:04:09 -0400 Subject: [PATCH] Add configurable TLS profiles for listeners Add a tls_profile option that controls the minimum TLS version and allowed cipher suites for STARTTLS/TLS listeners. Profiles align with the Mozilla SSL Configuration Generator guidelines. Profiles: default - Go standard library defaults (no explicit constraints) modern - TLS 1.3+ only (Mozilla modern) intermediate - TLS 1.2+ with AEAD + ECDHE suites only (Mozilla intermediate) legacy - TLS 1.0+ with all Go cipher suites including insecure ones --- config.go | 1 + main.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++- smtprelay.ini | 27 ++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index 38eb262..d6625e7 100644 --- a/config.go +++ b/config.go @@ -31,6 +31,7 @@ var ( listenStr = flagset.String("listen", "127.0.0.1:25 [::1]:25", "Address and port to listen for incoming SMTP") localCert = flagset.String("local_cert", "", "SSL certificate for STARTTLS/TLS") localKey = flagset.String("local_key", "", "SSL private key for STARTTLS/TLS") + tlsProfile = flagset.String("tls_profile", "default", "TLS profile: modern | intermediate | default | legacy") localForceTLS = flagset.Bool("local_forcetls", false, "Force STARTTLS (needs local_cert and local_key)") readTimeoutStr = flagset.String("read_timeout", "60s", "Socket timeout for read operations") writeTimeoutStr = flagset.String("write_timeout", "60s", "Socket timeout for write operations") diff --git a/main.go b/main.go index e87a4b8..226182e 100644 --- a/main.go +++ b/main.go @@ -281,6 +281,7 @@ func generateUUID() string { } func getTLSConfig() *tls.Config { + // Certificate loading / validation if *localCert == "" || *localKey == "" { log.Fatal(). Str("cert_file", *localCert). @@ -295,9 +296,60 @@ func getTLSConfig() *tls.Config { Msg("cannot load X509 keypair") } - return &tls.Config{ + // TLS profile configuration + // tls.Config.CipherSuites only affects TLS 1.0–1.2. + + // Base config: Go defaults unless overridden. + conf := &tls.Config{ Certificates: []tls.Certificate{cert}, } + + profile := strings.ToLower(strings.TrimSpace(*tlsProfile)) + if profile == "" { + profile = "default" + } + + switch profile { + case "default": + // Go defaults (leave MinVersion/MaxVersion/CipherSuites unset). + + case "modern": + // TLS 1.3+. + conf.MinVersion = tls.VersionTLS13 + + case "intermediate": + // TLS 1.2+ with AEAD + ECDHE cipher suites only. + // Mozilla "intermediate" — AEAD + ECDHE only. + conf.MinVersion = tls.VersionTLS12 + conf.CipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + } + + case "legacy": + // Last resort: TLS 1.0+ and everything Go exposes for TLS 1.0–1.2. + conf.MinVersion = tls.VersionTLS10 + + allSuites := []uint16{} + for _, cs := range tls.CipherSuites() { + allSuites = append(allSuites, cs.ID) + } + for _, cs := range tls.InsecureCipherSuites() { + allSuites = append(allSuites, cs.ID) + } + conf.CipherSuites = allSuites + + default: + log.Warn(). + Str("tls_profile", profile). + Msg("unknown tls_profile; using default") + } + + return conf } func watchAliasFile() { diff --git a/smtprelay.ini b/smtprelay.ini index 2334be5..dacb3e7 100644 --- a/smtprelay.ini +++ b/smtprelay.ini @@ -35,6 +35,33 @@ ;local_cert = smtpd.pem ;local_key = smtpd.key +; TLS PROFILE (STARTTLS / TLS listeners) +; Controls the minimum TLS version and allowed cipher suites for inbound +; connections when using listen protocols `starttls://` or `tls://`. +; Profiles follow the Mozilla SSL Configuration Generator guidelines. +; +; default +; Go standard library defaults. Tracks improvements as Go updates +; its TLS defaults. Recommended for most deployments. +; +; modern +; TLS 1.3+ only. Simplest and most secure, but rejects any client +; that cannot negotiate TLS 1.3. +; Matches Mozilla "modern" configuration. +; +; intermediate +; TLS 1.2+. For TLS 1.2 connections only AEAD ciphers (AES-GCM, +; ChaCha20-Poly1305) with ECDHE key exchange are allowed, +; providing forward secrecy. TLS 1.3 ciphers are always available. +; Matches Mozilla "intermediate" configuration. +; +; legacy +; TLS 1.0+ with all cipher suites the Go standard library supports, +; including insecure ones (RC4, 3DES). Use only when you must +; support very old clients that cannot negotiate TLS 1.2. +; +;tls_profile=default + ; Enforce encrypted connection on STARTTLS ports before ; accepting mails from client. ;local_forcetls = false