[译]巧用go:linkname 定制 TLS 1.3 加密算法套件

edgeidea · · 175 次点击 · · 开始浏览    

[译]原文:[Abusing go:linkname to customize TLS 1.3 cipher suites](https://www.joeshaw.org/abusing-go-linkname-to-customize-tls13-cipher-suites/) -- >``` 已获授权翻译转载 感谢Joe! 如翻译有误,还请大家不吝赐教!``` 当Go1.12发布后,我非常兴奋地去测试我们的新opt-in对TLS1.3支持情况。 TLS1.3对于当前的WEB安全协议方面是一个重大的提升。 我渴望测试我写的一个工具,该工具允许我去扫描服务器是否支持某些TLS参数。在TLS中,客户端提供一系列支持的加密算法套件(cipher suite)发给服务器,然后服务器选择一个最合适的使用。最合适的选择依据就是安全与性能的平衡。 为了枚举哪些加密算法套是服务器所支持的,客户端必须建立特殊连接,每次连接提供单一的加密算法套。如果服务器拒绝该连接,你就知道该加密算法套件服务器是不支持的。 对于TLS1.2,以下代码是非常清晰明确的: ```go func supportedTLS12Ciphers(hostname string) []uint16 { // Taken from https://golang.org/pkg/crypto/tls/#pkg-constants var allCiphers = []uint16{ tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, } var supportedCiphers []uint16 for _, c := range allCiphers { cfg := &tls.Config{ ServerName: hostname, CipherSuites: []uint16{c}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, } conn, err := net.Dial("tcp", hostname+":443") if err != nil { panic(err) } client := tls.Client(conn, cfg) client.Handshake() client.Close() if client.ConnectionState().CipherSuite == c { supportedCiphers = append(supportedCiphers, c) } } return supportedCiphers } ``` 在改写工具,编写那些烦人的代码来支持TLS1.3的过程中,我遗憾的发现Go居然不允许我指定TLS1.3加密算法套件发给服务器。需要弄清楚的是,TLS1.3单个加密算法套件包含哪些内容和支持多少组合 方面做了精简。除非TLS1.3密码套件存在漏洞,否则允许对其进行定制也不会有什么好处。 诚然,当前改写工具是有意义的。并且我想看看我是否能深入go:linkname,这一深埋在Go的编译文档之中的特性。 ``` //go:linkname localname importpath.name The //go:linkname directive instructs the compiler to use “importpath.name” as the object file symbol name for the variable or function declared as “localname” in the source code. Because this directive can subvert the type system and package modularity, it is only enabled in files that have imported “unsafe”. ``` 哈哈!这看起来有戏。如果在Go的标准库中有一个函数或变量能指定TLS1.3加密算法套件的列表,我们可以在自己的代码中重写(override)它,Go编译后,使用的是我们本地的重写实现,而不是标准库中的实现。 让我们来深入到TLS1.3的标准实现,文件路径```crypto/tls/handshake_client.go```[源码链接](https://github.com/golang/go/blob/06472b99cdf59f00049f3cd8c9e05ba283cb2c56/src/crypto/tls/handshake_client.go#L121-L122),实现如下 ```go if hello.supportedVersions[0] == VersionTLS13 { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13()...) // ... } ``` 好了,让我们来重写函数```defaultCipherSuitesTLS13()```,路径为```crypto/tls/common.go``` [源码链接](https://github.com/golang/go/blob/a6a7b148f874b32a34e833893971b471cd9cdeb7/src/crypto/tls/common.go#L1120-L1123) ```go func defaultCipherSuitesTLS13() []uint16 { once.Do(initDefaultCipherSuites) return varDefaultCipherSuitesTLS13 } ``` 这有点复杂,第一次使用时,初始化函数是延迟加载的,该初始化函数维护了一系列的默认加密算法套件,不仅仅是TLS1.3加密算法套件。我不想打乱原有逻辑,但在函数```initDefaultCipherSuites```中,有以下定义,[源码链接](https://github.com/golang/go/blob/a6a7b148f874b32a34e833893971b471cd9cdeb7/src/crypto/tls/common.go#L1150-L1154): ```go var DefaultCipherSuitesTLS13 = []uint16{ TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_256_GCM_SHA384, } ``` 哈哈,发现了一个定义加密算法套件的全局变量。因为这个初始化函数只被调用一次,我们可以初始化该列表,然后在我们的代码中控制它。 ``` // 为了应用go:linkname 必须导入 unsafe 包 import ( "crypto/tls" _ "unsafe" ) //我们将默认的defaultCipherSuitesTLS13函数从包crypto/tls引入到本地包。这将允许我们在想要初始化加密算法套件列表时候可以延时调用它。 //go:linkname defaultCipherSuitesTLS13 crypto/tls.defaultCipherSuitesTLS13 func defaultCipherSuitesTLS13() []uint16 //接着,我们引入varDefaultCipherSuitesTLS13切片到本地包,这就是我们想要重写修改的加密算法套件变量 //go:linkname varDefaultCipherSuitesTLS13 crypto/tls.varDefaultCipherSuitesTLS13 var varDefaultCipherSuitesTLS13 []uint16 //当然我们同时也定义一个切片,来保存修改前的默认加密算法套件,当我们完成工作后,可以用来恢复 var realDefaultCipherSuitesTLS13 []uint16 func init() { // 初始化 TLS1.3加密算法套件列表,varDefaultCipherSuitesTLS13将被覆盖 realDefaultCipherSuitesTLS13 = defaultCipherSuitesTLS13() } func supportedTLS13Ciphers(hostname string) []uint16 { var supportedCiphers []uint16 for _, c := range realDefaultCipherSuitesTLS13 { cfg := &tls.Config{ ServerName: hostname, MinVersion: tls.VersionTLS13, } // 重写TLS原包中的默认的加密算法套件切片 varDefaultCipherSuitesTLS13 = []uint16{c} conn, err := net.Dial("tcp", hostname+":443") if err != nil { panic(err) } client := tls.Client(conn, cfg) client.Handshake() client.Close() if client.ConnectionState().CipherSuite == c { supportedCiphers = append(supportedCiphers, c) } } // 恢复TLS原包中的默认的加密算法套件切片 varDefaultCipherSuitesTLS13 = realDefaultCipherSuitesTLS13 return supportedCiphers } ``` 正如你所看到的,我们用了```go:linkname```颠覆了原包的函数及变量的模块化设计。我们使用了包的```init```函数来填充默认的密码套件列表,然后用重载的变量来迭代,每次使用一个支持的密码套件尝试连接。最后,我们要确保清理所做的修改,并将默认列表设置为完整列表,还原重载的变化,以备将来使用。 最后,让我们来把他们集成到一起吧: ```go func main() { hostname := os.Args[1] fmt.Println("Supported TLS 1.2 ciphers") for _, c := range supportedTLS12Ciphers(hostname) { fmt.Printf(" %s\n", tls.CipherSuiteName(c)) } fmt.Println() fmt.Println("Supported TLS 1.3 ciphers") for _, c := range supportedTLS13Ciphers(hostname) { fmt.Printf(" %s\n", tls.CipherSuiteName(c)) } } ``` ``` $ go run cipherlist.go joeshaw.org Supported TLS 1.2 ciphers TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 Supported TLS 1.3 ciphers TLS_AES_128_GCM_SHA256 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_256_GCM_SHA384 ``` 最后总结一下: ```go:linkname```应该尽量少用。仔细考虑自己是否必须使用,或者你是否能用其它方法来解决你的问题。就我而言,另一种选择是导入所有的```crypto/tls```,然后做一些很少的修改。这样也许会在某个Go TLS的指针堆栈上迷惑我,并且给我带来后续升级的负担。虽然我知道通过```go:linkname```允许我使用TLS系列的当前版本和未来版本,没有```crypto/tls```内部的兼容保障,只要我使用的特定部分不改变,还是能接受的。 所有代码托管在[github](https://github.com/joeshaw/cipherlist)

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

175 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传