Day 38 — TLS Handshake
Learning Objectives
- Understand what happens during a TLS handshake (why cert expiry causes incidents)
- Inspect live TLS connections: version, cipher, expiry
- Build a cert expiry monitor — the tool that prevents your next outage
Key Concepts
TLS 1.3 Handshake (simplified):
Client → ClientHello (supported ciphers, TLS version, random)
Server ← ServerHello (chosen cipher, TLS version, random)
Server ← Certificate (server's cert chain)
Server ← CertificateVerify (signature proves server has the private key)
Server ← Finished
Client → Finished
─────── Handshake complete (~1 RTT ───────
Client ⟺ Server: encrypted application data
Why cert expiry causes outages:
- Browser/client rejects expired cert → connection fails
- No graceful fallback — hard failure
- "Certificate expired" is in the top 5 causes of security incidents
Certificate fields you need to know:
Subject → who the cert is for (CommonName, SANs)
Issuer → who signed it (CA)
NotBefore → valid from
NotAfter → valid until (expiry)
SANs → Subject Alternative Names (DNS names and IPs the cert covers)
KeyUsage → what the cert can be used for
Exercise: TLS Inspector + Cert Monitor
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"time"
)
type CertInfo struct {
Host string
TLSVersion string
CipherSuite string
Subject string
Issuer string
NotAfter time.Time
SANs []string
DaysLeft int
Warning string
}
func tlsVersionName(v uint16) string {
switch v {
case tls.VersionTLS10: return "TLS 1.0 (INSECURE)"
case tls.VersionTLS11: return "TLS 1.1 (DEPRECATED)"
case tls.VersionTLS12: return "TLS 1.2"
case tls.VersionTLS13: return "TLS 1.3"
default: return fmt.Sprintf("unknown(0x%x)", v)
}
}
func inspectTLS(host string) (*CertInfo, error) {
conf := &tls.Config{
ServerName: host,
// InsecureSkipVerify: false — NEVER skip verification in production
}
conn, err := tls.DialWithDialer(
&net.Dialer{Timeout: 10 * time.Second},
"tcp", host+":443", conf,
)
if err != nil {
return nil, fmt.Errorf("TLS connect: %w", err)
}
defer conn.Close()
state := conn.ConnectionState()
cert := state.PeerCertificates[0] // leaf cert (server's cert)
daysLeft := int(time.Until(cert.NotAfter).Hours() / 24)
info := &CertInfo{
Host: host,
TLSVersion: tlsVersionName(state.Version),
CipherSuite: tls.CipherSuiteName(state.CipherSuite),
Subject: cert.Subject.CommonName,
Issuer: cert.Issuer.CommonName,
NotAfter: cert.NotAfter,
SANs: cert.DNSNames,
DaysLeft: daysLeft,
}
if daysLeft < 7 {
info.Warning = fmt.Sprintf("CRITICAL: cert expires in %d days!", daysLeft)
} else if daysLeft < 30 {
info.Warning = fmt.Sprintf("WARNING: cert expires in %d days", daysLeft)
}
return info, nil
}
func main() {
hosts := []string{"example.com", "golang.org", "cloudflare.com"}
for _, host := range hosts {
info, err := inspectTLS(host)
if err != nil {
fmt.Printf("[%s] ERROR: %v\n", host, err)
continue
}
fmt.Printf("[%s]\n", info.Host)
fmt.Printf(" TLS: %s\n", info.TLSVersion)
fmt.Printf(" Cipher: %s\n", info.CipherSuite)
fmt.Printf(" Subject: %s\n", info.Subject)
fmt.Printf(" Issuer: %s\n", info.Issuer)
fmt.Printf(" Expiry: %s (%d days)\n", info.NotAfter.Format("2006-01-02"), info.DaysLeft)
fmt.Printf(" SANs: %v\n", info.SANs)
if info.Warning != "" {
fmt.Printf(" *** %s ***\n", info.Warning)
}
fmt.Println()
}
}
Real-World Context
Cert expiry is one of the top causes of security incidents. Facebook had a major outage in 2019 partly due to a cert issue. LinkedIn, Spotify, and many others have had cert expiry outages. Build a monitoring tool that checks every cert in your fleet daily and alerts at 30 days. This code is that tool.