使用 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

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

golang http1.1的请求负载均衡按照http2.0转发问题

Golang + nginx + https

Golang + nginx的+ HTTPS