使用 GoLang 獲取 TLS 的 Client Hello Info
阿新 • • 發佈:2020-07-12
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))
}
}