Day 39 — TLS Server in Go
Learning Objectives
- Configure
tls.Configto enforce security policy - Require TLS 1.2+ — reject connections from legacy clients
- Log TLS metadata per connection for security auditing
Key Concepts
// tls.Config security settings
cfg := &tls.Config{
MinVersion: tls.VersionTLS12, // reject TLS 1.0, 1.1
MaxVersion: tls.VersionTLS13, // allow up to TLS 1.3
// Cipher suite selection (TLS 1.2 only; TLS 1.3 ciphers are fixed)
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
// NEVER include: RC4, DES, 3DES, NULL, EXPORT ciphers
},
// ConnState callback — called for each connection state transition
// Use to log TLS metadata for security auditing
}
// NEVER in production:
// InsecureSkipVerify: true — disables cert verification (OWASP A02)
Exercise: Hardened TLS Echo Server
package main
import (
"bufio"
"crypto/tls"
"fmt"
"net"
"time"
)
func buildTLSConfig(certFile, keyFile string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, fmt.Errorf("load cert: %w", err)
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
// Prefer TLS 1.3 (set automatically if both sides support it)
// Called after TLS handshake completes — log security metadata
VerifyConnection: func(cs tls.ConnectionState) error {
fmt.Printf("[TLS] version=%s cipher=%s server_name=%s\n",
tlsVersionName(cs.Version),
tls.CipherSuiteName(cs.CipherSuite),
cs.ServerName,
)
return nil
},
}, nil
}
func handleTLSConn(conn net.Conn) {
defer conn.Close()
tlsConn, ok := conn.(*tls.Conn)
if !ok { return }
// Force TLS handshake to complete (it's lazy by default)
conn.SetDeadline(time.Now().Add(10 * time.Second))
if err := tlsConn.Handshake(); err != nil {
fmt.Printf("[TLS] handshake failed: %v\n", err)
return
}
conn.SetDeadline(time.Time{}) // clear deadline after handshake
state := tlsConn.ConnectionState()
fmt.Printf("[CONN] %s via %s\n", conn.RemoteAddr(), tlsVersionName(state.Version))
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
fmt.Fprintf(conn, "ECHO: %s\n", scanner.Text())
}
}
func main() {
// Generate a self-signed cert for testing (see Day 40 for how)
// For now, use: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
cfg, err := buildTLSConfig("cert.pem", "key.pem")
if err != nil {
fmt.Println("TLS config error:", err)
return
}
ln, err := tls.Listen("tcp", ":8443", cfg)
if err != nil {
fmt.Println("listen error:", err)
return
}
defer ln.Close()
fmt.Println("TLS echo server on :8443")
fmt.Println("Test: openssl s_client -connect localhost:8443")
for {
conn, err := ln.Accept()
if err != nil { return }
go handleTLSConn(conn)
}
}
Test with openssl:
# Connect and inspect TLS details
openssl s_client -connect localhost:8443 -tls1_2
openssl s_client -connect localhost:8443 -tls1_3
# Try to connect with TLS 1.1 — should fail
openssl s_client -connect localhost:8443 -tls1_1
Real-World Context
PCI DSS 4.0 requires TLS 1.2+ for all payment data transmissions. NIST SP 800-52 recommends TLS 1.2+ for government systems. MinVersion: tls.VersionTLS12 is a one-line compliance control. Many teams skip this and get flagged in audits.