使用 GoLang 获取 TLS 的 Client Hello Info
Posted flipped
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 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(‘
‘)
if err != nil {
log.Println(err)
return
}
fmt.Println(msg)
_, err = conn.Write([]byte("world
"))
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
"))
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(‘
‘)
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))
}
}
参考
以上是关于使用 GoLang 获取 TLS 的 Client Hello Info的主要内容,如果未能解决你的问题,请参考以下文章
是否可以使用 net/http 在 golang 中托管多个域 TLS?
Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)