Frontend Labs
⋯

Day 39 — TLS Server in Go

Learning Objectives

  • Configure tls.Config to 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.


Generated from week6.md · 8 Oct 2026 16:36