zoukankan      html  css  js  c++  java
  • 使用 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))
    	}
    }
    
    

    参考

  • 相关阅读:
    nginx利用image_filter动态生成缩略图
    uva 624 CD 01背包打印路径
    【剑指Offer学习】【全部面试题汇总】
    POJ2096-Collecting Bugs(概率DP)
    配置struts tags 输出HTML
    合并基因表达水平(merge gene expression levels, FPKM)
    设置MySQL自动增长从某个指定的数开始
    列联表(Crosstabs)
    mysql 实现行号的方法——如何获取当前记录所在行号
    shell 标出输入、标准输出、错误输出
  • 原文地址:https://www.cnblogs.com/flipped/p/13288066.html
Copyright © 2011-2022 走看看