DEV Community

Cover image for Complete TLS Workflow in Golang Made Simple: Full Process Explained
Leapcell
Leapcell

Posted on

Complete TLS Workflow in Golang Made Simple: Full Process Explained

Image description

Leapcell: The Best of Serverless Web Hosting

Explanation of the TLS Handshake Process

The TLS (Transport Layer Security) handshake is a vital procedure that enables secure communication between a client (such as a web browser) and a server (such as a web server). Below is a detailed breakdown of the entire TLS handshake process:

  1. Client Hello

    • The client starts the handshake by sending a "Client Hello" message to the server.
    • This message contains:
      • The TLS versions supported by the client.
      • A list of cipher suites (encryption algorithms) it supports.
      • A random byte string (referred to as Client Random).
  2. Server Hello

    • The server replies with a "Server Hello" message.
    • This message includes:
      • The selected TLS version.
      • The chosen cipher suite.
      • A random byte string (known as Server Random).
      • The server's digital certificate (issued by a trusted Certificate Authority, CA).
  3. Certificate Verification

    • The client verifies the server's certificate through the certificate authority (CA) chain.
    • It ensures that the certificate is valid, not expired, and issued to the correct domain.
  4. Pre - Master Secret Generation

    • The client generates a "Pre - Master Secret" using the server's public key (extracted from the certificate).
    • This secret is encrypted and sent to the server.
  5. Master Secret Derivation

    • Both the client and the server generate the "Master Secret" using the following:
      • The Client Random.
      • The Server Random.
      • The Pre - Master Secret.
    • The Master Secret is used to derive session keys for encryption and integrity checks.
  6. Session Keys Creation

    • Using the Master Secret, both parties create:
      • Encryption keys for symmetric encryption.
      • MAC (Message Authentication Code) keys for integrity checks.
  7. Client Finished

    • The client sends a "Finished" message, which is encrypted with the session keys.
    • This confirms that the handshake was successful and that future messages will be encrypted.
  8. Server Finished

    • The server sends its own "Finished" message, encrypted with the session keys.
    • This marks the end of the handshake and the start of encrypted communication.
  9. Data Transfer

    • All subsequent communication is encrypted using the derived session keys.
    • Data is sent in encrypted packets with integrity checks.

Diagram of the TLS Handshake Process

+----------------------------------------+ +----------------------------------------+ | Client | | Server | +----------------------------------------+ +----------------------------------------+ | | | | | ClientHello |----->| | | [TLS Version, Cipher Suites, Random] | | | | | | | | | | ServerHello | | |<-----| [TLS Version, Cipher Suite, Random] | | | | | | |<-----| Certificate | | | | [Server's Public Key] | | | | | | |<-----| ServerHelloDone | | | | | | CertificateVerify | | | | [Verify Server's Certificate] | | | | | | | | ClientKeyExchange |----->| | | [Encrypted Pre-Master Secret] | | | | | | | | ChangeCipherSpec |----->| | | [Start Using Encryption] | | | | | | | | Finished |----->| | | [Verifies Handshake Integrity] | | | | | | | | |<-----| ChangeCipherSpec | | | | [Start Using Encryption] | | | | | | |<-----| Finished | | | | [Verifies Handshake Integrity] | | | | | | Secure Communication |<--->| Secure Communication | | [Encrypted Data Transfer] | | [Encrypted Data Transfer] | +----------------------------------------+ +----------------------------------------+ 
Enter fullscreen mode Exit fullscreen mode

Obtaining the TLS Client Hello Message with GoLang

Here's how to implement a server that captures all ClientHello messages using GoLang:

Certificate Generation

First, generate the necessary SSL certificates:

# Generate a private key openssl genrsa -out server.key 2048 # Generate a public key (certificate) openssl req -new -x509 -key server.key -out server.pem -days 3650 
Enter fullscreen mode Exit fullscreen mode

Server Implementation

The following is the complete server code for capturing ClientHello information:

package main import ( "bufio" "crypto/tls" "encoding/json" "fmt" "io/ioutil" "log" "net" "os" "sync" "time" ) type CollectInfos struct { ClientHellos []*tls.ClientHelloInfo sync.Mutex } var collectInfos CollectInfos var currentClientHello *tls.ClientHelloInfo func (c *CollectInfos) collectClientHello(clientHello *tls.ClientHelloInfo) { c.Lock() defer c.Unlock() c.ClientHellos = append(c.ClientHellos, clientHello) } func (c *CollectInfos) DumpInfo() { c.Lock() defer c.Unlock() data, err := json.Marshal(c.ClientHellos) if err != nil { log.Fatal(err) } ioutil.WriteFile("hello.json", data, os.ModePerm) } func getCert() *tls.Certificate { cert, err := tls.LoadX509KeyPair("server.pem", "server.key") if err != nil { log.Println(err) return nil } return &cert } func buildTlsConfig(cert *tls.Certificate) *tls.Config { cfg := &tls.Config{ Certificates: []tls.Certificate{*cert}, GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) { collectInfos.collectClientHello(clientHello) currentClientHello = clientHello return nil, nil }, } return cfg } func serve(cfg *tls.Config) { ln, err := tls.Listen("tcp", ":443", cfg) if err != nil { log.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() if err != nil { log.Println(err) continue } go handler(conn) } } func handler(conn net.Conn) { defer conn.Close() r := bufio.NewReader(conn) for { msg, err := r.ReadString('\n') if err != nil { log.Println(err) return } fmt.Println(msg) data, err := json.Marshal(currentClientHello) if err != nil { log.Fatal(err) } _, err = conn.Write(data) if err != nil { log.Println(err) return } } } func main() { go func() { for { collectInfos.DumpInfo() time.Sleep(10 * time.Second) } }() cert := getCert() if cert != nil { serve(buildTlsConfig(cert)) } } 
Enter fullscreen mode Exit fullscreen mode

Client Implementation

The corresponding client code is as follows:

func main() { conn, err := tls.Dial("tcp", "localhost:443", &tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } defer conn.Close() _, err = conn.Write([]byte("hello\n")) if err != nil { log.Fatal(err) } buf := make([]byte, 1000) n, err := conn.Read(buf) if err != nil { log.Fatal(err) } fmt.Println(string(buf[:n])) } 
Enter fullscreen mode Exit fullscreen mode

This implementation enables you to capture detailed information from ClientHello messages during the TLS handshake process. The server periodically exports this information to a JSON file for analysis.

Leapcell: The Best of Serverless Web Hosting

Finally, recommend a platform that is most suitable for deploying Go services: Leapcell

Image description

🚀 Build with Your Favorite Language

Develop effortlessly in JavaScript, Python, Go, or Rust.

🌍 Deploy Unlimited Projects for Free

Only pay for what you use—no charges when there are no requests.

⚡ Pay - as - You - Go, No Hidden Costs

No idle fees, just seamless scalability.

Image description

📖 Explore Our Documentation

🔹 Follow us on Twitter: @LeapcellHQ

Top comments (0)