提取Go语言中的TLS密钥

huangapple go评论136阅读模式
英文:

Extracting TLS secrets in Go

问题

我不完全确定这是否真的是一个关于Wireshark、Go或Syncthing的问题;我尝试过Wireshark开发者列表Go开发者列表,但没有得到回应,所以我想在这里试试:

我正在开发一个Wireshark Syncthing解析器。由于大部分Syncthing协议都封装在TLS中,我需要将TLS密钥提供给Wireshark。

我阅读了Wireshark的TLS文档;Syncthing是用Go编写的,所以我对其进行了补丁以导出TLS密钥,像这样(这只是Syncthing上游代码加上了最后两行):

// TLS配置用于监听套接字和出站连接。

var tlsCfg *tls.Config
if a.cfg.Options().InsecureAllowOldTLSVersions {
    l.Infoln("允许在同步连接上使用TLS 1.2。这不是最优安全性。")
    tlsCfg = tlsutil.SecureDefaultWithTLS12()
} else {
    tlsCfg = tlsutil.SecureDefaultTLS13()
}
tlsCfg.Certificates = []tls.Certificate{a.cert}
tlsCfg.NextProtos = []string{bepProtocolName}
tlsCfg.ClientAuth = tls.RequestClientCert
tlsCfg.SessionTicketsDisabled = true
tlsCfg.InsecureSkipVerify = true

// 下面两行打开一个文件,并配置应用程序将其TLS密钥转储到该文件中
// 参见:https://pkg.go.dev/crypto/tls#example-Config-KeyLogWriter

w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
tlsCfg.KeyLogWriter = w

这个方法是有效的,指定的文件中写入了各种内容,但是将该文件提供给Wireshark并不能启用TLS解密。我检查了文件,发现它包含CLIENT_HANDSHAKE_TRAFFIC_SECRETSERVER_HANDSHAKE_TRAFFIC_SECRETCLIENT_TRAFFIC_SECRET_0SERVER_TRAFFIC_SECRET_0行,但没有关键的CLIENT_RANDOM行。我是不是做错了什么或者漏掉了什么?

英文:

I'm not totally sure whether this is really a Wireshark, Go, or Syncthing question; I tried the Wireshark dev list and the Go dev list but got no response, so I figured I'll try here:

I'm working on a Wireshark Syncthing dissector. Since most of the Syncthing protocols are encapsulated in TLS, I need to provide the TLS secrets to Wireshark.

I read the Wireshark TLS documentation; Syncthing is written in Go, so I patched it to export TLS secrets, like this (this is just Syncthing upstream code with the addition of the two final lines):

// The TLS configuration is used for both the listening socket and outgoing
// connections.

var tlsCfg *tls.Config
if a.cfg.Options().InsecureAllowOldTLSVersions {
	l.Infoln("TLS 1.2 is allowed on sync connections. This is less than optimally secure.")
	tlsCfg = tlsutil.SecureDefaultWithTLS12()
} else {
	tlsCfg = tlsutil.SecureDefaultTLS13()
}
tlsCfg.Certificates = []tls.Certificate{a.cert}
tlsCfg.NextProtos = []string{bepProtocolName}
tlsCfg.ClientAuth = tls.RequestClientCert
tlsCfg.SessionTicketsDisabled = true
tlsCfg.InsecureSkipVerify = true

// The following two lines open a file in the current directory and configure the application to dump its TLS secrets there
// See: https://pkg.go.dev/crypto/tls#example-Config-KeyLogWriter

w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
tlsCfg.KeyLogWriter = w

This works, and various stuff is written to the specified file, but providing that file to Wireshark doesn't enable TLS decryption. I examined the file, and I see that it contains CLIENT_HANDSHAKE_TRAFFIC_SECRET, SERVER_HANDSHAKE_TRAFFIC_SECRET, CLIENT_TRAFFIC_SECRET_0, and SERVER_TRAFFIC_SECRET_0 lines, but not the crucial CLIENT_RANDOM lines. Am I doing something wrong or missing something?

答案1

得分: 1

根本原因是tls1.2tls1.3之间的差异,可以在这里找到差异。

根据golang的tls代码,以下是一些测试样例:

首先,启动一个HTTPS服务器:

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
    if req.URL.Path != "/" {
        http.NotFound(w, req)
        return
    }
})

w, err := os.OpenFile("/keypath/https-key.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
    fmt.Printf("failed to open file err %+v", err)
    return
}

cs := make([]uint16, len(cipherSuites))
copy(cs, cipherSuites)
var tlsCfg tls.Config
tlsCfg.Certificates = make([]tls.Certificate, 1)
tlsCfg.Certificates[0], err = tls.LoadX509KeyPair(*certFile, *keyFile)
tlsCfg.NextProtos = []string{"h2"}
tlsCfg.ClientAuth = tls.RequestClientCert
tlsCfg.SessionTicketsDisabled = true
tlsCfg.InsecureSkipVerify = true
tlsCfg.KeyLogWriter = w
tlsCfg.MinVersion = tls.VersionTLS13
tlsCfg.CipherSuites = cs
tlsCfg.PreferServerCipherSuites = true

srv := &http.Server{
    Addr:      *addr,
    Handler:   mux,
    TLSConfig: &tlsCfg,
}

log.Printf("Starting server on %s", *addr)
err = srv.ListenAndServeTLS("", "")
log.Fatal(err)

然后,使用curl进行测试,使用tls1.3curl -Lv https://localhost:4000 --cacert /crtpath/ca.crt --tlsv1.3

我们可以在https-key.txt中找到以下内容:

> cat https-key.txt
CLIENT_HANDSHAKE_TRAFFIC_SECRET xxxx yyyyy
SERVER_HANDSHAKE_TRAFFIC_SECRET xxxx yyyyyyyyy
CLIENT_TRAFFIC_SECRET_0 xxxxxxx yyyy
SERVER_TRAFFIC_SECRET_0 xx yyyyyyyyyyyy

然后,在WireShark中将keyFile设置为Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filename,现在WireShark可以进行TLS解密。

对于TLS1.2的测试,您可以将服务器代码更改为tlsCfg.MinVersion = tls.VersionTLS12,然后使用tls1.2进行测试:curl -Lv https://localhost:4000 --cacert /crtpath/ca.crt --tlsv1.2。再次检查https-key.txt,您会发现内容可能是CLIENT_RANDOM xxxxxx yyyyyyyy

英文:

The root cause is the difference between tls1.2 and tls1.3, the difference could be found here
提取Go语言中的TLS密钥

Per golang tls code

	keyLogLabelTLS12           = "CLIENT_RANDOM"
	keyLogLabelClientHandshake = "CLIENT_HANDSHAKE_TRAFFIC_SECRET"
	keyLogLabelServerHandshake = "SERVER_HANDSHAKE_TRAFFIC_SECRET"
	keyLogLabelClientTraffic   = "CLIENT_TRAFFIC_SECRET_0"
	keyLogLabelServerTraffic   = "SERVER_TRAFFIC_SECRET_0"
  • For tls1.3, those parameters CLIENT_HANDSHAKE_TRAFFIC_SECRET, SERVER_HANDSHAKE_TRAFFIC_SECRET, CLIENT_TRAFFIC_SECRET_0, and SERVER_TRAFFIC_SECRET_0 could be exported as client secrets
  • For tls1.2, CLIENT_RANDOM could be exported as client secrets

All of them could be used in Wireshark to decrypt TLS1.2 and TLS 1.3.


Here is one test sample

First, start one HTTPS server

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/" {
			http.NotFound(w, req)
			return
		}
	})

	w, err := os.OpenFile("/keypath/https-key.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		fmt.Printf("failed to open file err %+v", err)
		return
	}

	cs := make([]uint16, len(cipherSuites))
	copy(cs, cipherSuites)
	var tlsCfg tls.Config
	tlsCfg.Certificates = make([]tls.Certificate, 1)
	tlsCfg.Certificates[0], err = tls.LoadX509KeyPair(*certFile, *keyFile)
	tlsCfg.NextProtos = []string{"h2"}
	tlsCfg.ClientAuth = tls.RequestClientCert
	tlsCfg.SessionTicketsDisabled = true
	tlsCfg.InsecureSkipVerify = true
	tlsCfg.KeyLogWriter = w
	tlsCfg.MinVersion = tls.VersionTLS13
	tlsCfg.CipherSuites = cs
	tlsCfg.PreferServerCipherSuites = true

	srv := &http.Server{
		Addr:      *addr,
		Handler:   mux,
		TLSConfig: &tlsCfg,
	}

	log.Printf("Starting server on %s", *addr)
	err = srv.ListenAndServeTLS("", "")
	log.Fatal(err)

Then, test it through curl with tls1.3 curl -Lv https://localhost:4000 --cacert /crtpath/ca.crt --tlsv1.3

We could find the content of https-key.txt as below

> cat https-key.txt
CLIENT_HANDSHAKE_TRAFFIC_SECRET xxxx yyyyy
SERVER_HANDSHAKE_TRAFFIC_SECRET xxxx yyyyyyyyy
CLIENT_TRAFFIC_SECRET_0 xxxxxxx yyyy
SERVER_TRAFFIC_SECRET_0 xx yyyyyyyyyyyy

Then set the keyFile /keypath/grpc-key.txt to Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filename in WireShark, now the Wireshark could do TLS decryption

For TLS1.2 test, you could change the server code to tlsCfg.MinVersion = tls.VersionTLS12 and then test it through curl with tls1.2 curl -Lv https://localhost:4000 --cacert /crtpath/ca.crt --tlsv1.2. And check the https-key.txt again, you could find the content could be CLIENT_RANDOM xxxxxx yyyyyyyy.

答案2

得分: 0

我弄清楚了,得到了@zangw的很多帮助。有两个问题:

  • 尽管一些关于Wireshark中TLS解密的讨论提到了CLIENT_RANDOM,但这只适用于TLS 1.2;当前的Syncthing通常使用TLS 1.3,其中涉及到我看到的其他密钥(CLIENT_HANDSHAKE_TRAFFIC_SECRETSERVER_HANDSHAKE_TRAFFIC_SECRETCLIENT_TRAFFIC_SECRET_0SERVER_TRAFFIC_SECRET_0)- 参见官方NSS密钥日志格式文档
  • 我开始捕获的时间太晚了(因为我在等待密钥被写入文件以便能够提供给Wireshark),所以Wireshark错过了初始的TLS握手,导致协议被错误地识别为TLS 1.2而不是1.3,并且无法解密流量。正确的做法是在TLS协商开始之前开始捕获,然后在创建密钥文件后将其提供给Wireshark。(这可能需要保存和重新加载捕获文件。)

Wireshark现在成功解密了TLS数据;可以通过选择“Encrypted Application Data”,然后点击窗口底部的“Decrypted TLS”选项卡来查看。

英文:

I figured it out, with a lot of help from @zangw. There were two problems:

  • Although some discussions of TLS decryption in Wireshark mention CLIENT_RANDOM, this is only for TLS 1.2; current Syncthing generally uses TLS 1.3, which involves the other secrets I was seeing (CLIENT_HANDSHAKE_TRAFFIC_SECRET, SERVER_HANDSHAKE_TRAFFIC_SECRET, CLIENT_TRAFFIC_SECRET_0, and SERVER_TRAFFIC_SECRET_0) - see the official documentation of the NSS Key Log Format.
  • I was starting the capture too late (since I was waiting for the secrets to be written out to the file in order to be able to provide them to Wireshark), and so Wireshark was missing the initial TLS handshake, which resulted in the misidentification of the protocol as TLS 1.2 instead of 1.3 as well as the inability to decrypt the traffic. The correct way to proceed is to start the capture before the TLS negotiation begins, and then subsequently provide Wireshark with the secrets file after it is created. (This may require saving and reloading the capture.)

Wireshark now successfuly decrypts the TLS data; it can be viewed by selecting "Encrypted Application Data" and then clicking on the "Decrypted TLS" tab at the bottom of the window.

huangapple
  • 本文由 发表于 2022年6月23日 00:45:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/72719194.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定