1. 程式人生 > 實用技巧 >使用 GoLang 獲取 TLS 的 Client Hello Info

使用 GoLang 獲取 TLS 的 Client Hello Info

TLS 介紹

TLS(Transport Layer Security)是一個保證資訊保安的應用層協議。它的前身是 SSL(Secure Socket Layer)。它是一套定義瞭如何對由 TCP 傳輸的報文進行加密的協議。

HTTP 協議傳輸報文時,資料是明文傳遞的,意味著你和伺服器之間的通訊是可以被別人截獲、監聽、篡改的。所以沒有安全性。因此就有了 SSL,後來發展為了 TLS。我們平時使用的 HTTPS 其實就是 HTTP+SSL/TCP 的簡稱。

TLS 握手過程

簡而言之,伺服器和客戶端通過 TLS 協議進行溝通時,客戶端發給伺服器一個隨機數,然後雙方用這個隨機數生成一個金鑰,之後就用它對報文做對稱加密。為了防止隨機數被竊聽,它倆會先互相 hello,傳遞版本號、支援的加密方法等,然後伺服器給客戶端自己的證書(RSA 加密演算法裡的公鑰),客戶端用伺服器的證書加密自己的證書及隨機數,發給伺服器。

用 GoLang 獲取 TLS 的 Client Hello 報文

下面我們實現一個可以獲取所有 ClientHello 報文資訊的伺服器。

證書生成

# 生成私鑰
openssl genrsa -out server.key 2048
# 生成公鑰(證書)
openssl req -new -x509 -key server.key -out server.pem -days 3650

使用 crypto/tls

GoLang 中的 crypto/tls 庫實現了 TLS 協議。因此只要參考它的文件就可以實現客戶端和伺服器。

伺服器:

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)
		_, err = conn.Write([]byte("world\n"))
		if err != nil {
			log.Println(err)
			return
		}
	}
}

func main() {
	cert, err := tls.LoadX509KeyPair("server.pem", "server.key")
	if err != nil {
		log.Fatal(err)
		return
	}
	ln, err := tls.Listen("tcp", ":443", &tls.Config{
		Certificates: []tls.Certificate{cert},
	})
	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 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]))
}

通過 GetConfigForClient 回撥函式獲取 ClientHelloInfo

tls.Config 中有個 GetConfigForClient 屬性,通過它我們可以拿到 ClientHelloInfo,然後可以存在本地,比如說定期匯出到 json 檔案裡。

下面是完整的伺服器的例子:

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))
	}
}

參考