TLS1.3 协议的Golang 实现——ClientHello
前言撰写本文时TLS1.3 RFC已经发布到28版本。以前写过一点密码学及TLS 相关的文章,为了更深入理解TLS1.3协议,这次将尝试使用Go语言实现它。网络上已有部分站点支持TLS1.3,Chrome浏览器通过设置可支持TLS1.3 (draft23),利用这些条件可验证,我们实现的协议是否标准。 完整的实现TLS1.3 工作量很大,概念和细节非常多(感觉又在挖坑)。本文首先会从ClientHello开始,后续可能会考虑 :Authentication、Cryptographic Computations、0-RTT 。 5G 未来每次基础设施的升级都是时代变革的前奏。 在移动互联网2G/3G时代,很多创新都约束在了Wifi 下;移动互联网进入4G时代后,爆发了各种直播、短视频等创新。现在的IOT和移动互联网上半场略有相似,待5G成熟万物互联后,相信也会爆发出一系列的创新。 网络互连后信息的安全传输,也是不容忽视的问题。TLS1.3 相对于之前的版本,修正了很多安全陷阱,降低了握手的次数,提高了效率。 私有化TLS1.3 中有不少设计是为了向下兼容TLS1.2 或TLS1.1 (毕竟是公开的网络协议),如果想要建立一个私有化的安全层,只要按照标准里的要点,可以剔除其中兼容性的设计,优化包结构减少数据传输量。例如 参考以太坊的RLP编码方式,将长度和数据结合在一起,就可以进一步减少包大小。 利用TLS1.3建立一个安全高效的“安全会话层”, 下面我们将尽可能的参照TLS1.3定义的结构编写代码,同时利用Wireshark 抓包查看包情况。 从ClientHello开始TLS1.3 的握手流程由客户端 发送ClientHello 开始,该消息携带密钥协商必要的数据。服务器端收到该消息后回复ServerHello。我们将向一个启用了TLS1.3 协议的站点发送,自己实现的 ClientHello 消息,看能否收到 ServerHello回复。 先看结果上图是Wireshark 的抓包结果,ali-BF 是我本机电脑,Server是某台启用TLS1.3的网络服务器。 从图中可以看到三次tcp 握手后, 我们发出ClientHello 消息长度348个字节,经过一个ack后成功收到 Server Hello 消息。 编码实现TLS1.3 包含一系列子协议,如 Record Protocol、Handshake Protocol 、Alert Protocol 、ApplicationData Protocol 等 发送一个ClientHello 至少需要实现以下模块 Record 层ClientHello 是明文传输的,所以是封装在TLSPlaintext 中 // ContentType enum {...} ; type ContentType byte const ( invalid ContentType = 0 changeCipherSpec ContentType = 20 alert ContentType = 21 handshake ContentType = 22 applicationData ContentType = 23 ) // TLSPlaintext plaintext on record layer type TLSPlaintext struct { contentType ContentType legacyRecordVersion ProtocolVersion //static length uint16 fragment syntax.Vector } type TLSCiphertext TLSPlaintext legacyRecordVersion 的值为0x0303 为了兼容TLS1.2版。 定义一个接口用于序列化,后面所有的Struct 都会实现该接口 type Encoder interface { //Encode coding object into the Writer,Encode(w io.Writer) error //ByteCount return the byte length of all Object ByteCount() int } type Vector Encoder 序列化TLSPlaintext func generateTLSPlaintext(contentType ContentType,fragment syntax.Vector) TLSPlaintext { return TLSPlaintext{ contentType: contentType,legacyRecordVersion: TLS1_2,length: uint16(fragment.ByteCount()),fragment: fragment,} } func (t *TLSPlaintext) Encode(w io.Writer) error { if uint16(t.length) > 2<<14 { return errors.New("overflow fragment") } err := syntax.Encode(w,syntax.WriteTo(t.contentType),syntax.WriteTo(t.legacyRecordVersion),syntax.WriteTo(t.length)) if err != nil { return err } err = t.fragment.Encode(w) return err } func (t *TLSPlaintext) ByteCount() int { return t.fragment.ByteCount() + 5 } 本文的主要目的是深入浅出的学习TLS1.3协议,因此在实现上并不是很关注性能和效率问题及部分异常情况。 HandshakeTLS1.3 支持三种方式的密钥协商:PSK-Only、(EC)DHE, 和PSK with (EC)DHE,本文主要是关注 ECDHE。 type Handshake struct { msgType HandshakeType /* handshake type */ length uint24 handshakeData syntax.Vector } func generateHandshake(msgType HandshakeType,data syntax.Vector) Handshake { l := data.ByteCount() return Handshake{ msgType: msgType,length: uint24{byte(l >> 16),byte(l >> 8),byte(l)},handshakeData: data,} } ClientHelloClientHello 对应Handshake的handshakeData 。 type extensionsVector struct { length uint16 extensions []extension.Extension } type ClientHello struct { legacyVersion tls.ProtocolVersion random [32]byte legacySessionId legacySessionId cipherSuites CipherSuiteVector legacyCompressionMethods legacyCompressionMethods extensions extensionsVector } legacyVersion 、legacySessionIdlegacyCompressionMethods 的定义是为了兼容旧版本,因此需要的是random 、 cipherSuites 和 extensions. CipherSuite 指明了Client所能支持的加密套件 例如:TLS_AES_128_GCM_SHA256、TLS_AES_256_GCM_SHA384等,只支持 AEAD 的加密算法套件。 func generateClientHello(cipherSuites []CipherSuite,exts ...extension.Extension) ClientHello { var r [32]byte rand.Read(r[:]) extBytes := 0 for _,ext := range exts { extBytes += ext.ByteCount() } return ClientHello{ legacyVersion: tls.TLS1_2,random: r,legacySessionId: legacySessionId{0,nil},cipherSuites: NewCipherSuite(cipherSuites...),legacyCompressionMethods: generateLegacyCompressionMethods([]byte{0}),extensions: generateExtensions(exts...),} } func generateExtensions(exts ...extension.Extension) extensionsVector { l := 0 for _,ext := range exts { l += ext.ByteCount() } return extensionsVector{ length: uint16(l),extensions: exts,} } func NewClientHelloHandshake(cipherSuites []CipherSuite,exts ...extension.Extension) Handshake { cl := generateClientHello(cipherSuites,exts...) return generateHandshake(clientHello,&cl) } 各种ExtensionClientHello 主要是通过Extension 传递密钥协商必要的素材, 第一次ClientHello 至少需要包含以下5个Extension: 通常情况下一台服务器会寄宿多个站点,即同一个IP 多个Web服务器。由于TLS 层还未完成握手,此时还没有http的请求(host head),无法知道具体站点。后续握手时服务器端将难以确定要发送的证书。 虽然采用通用名和subjectAltName的方式可以支持多个域名,但是无法得知未来所有的域名,一旦有变更还得重新申请证书。因此在ClientHello中添加ServerName扩展用于指明要访问的主机,查看详情RFC6066。 这样做有个缺点,ClientHello 是明文传输,中间人可以明确探知该流量的目的地。像WhatApp 和 Signal 就采用一种叫“域前置” 的技术去绕过该问题。 2、SupportedVersions :所能支持的TLS 版本如:TLS1.1、TLS1.2、TLS1.3等 用于协商最终采用的TLS 版本,在ClientHello 所能支持的列表,把最优先支持的放在第一位。 3、SignatureAlgorithms : 所支持的签名算法 如:ECDSA_SECP256R1_SHA256、ECDSA_SECP384R1_SHA384等 4、SupportedGroups:用于密钥交换 本文主要关注椭圆曲线 如:SECP256R1、SECP384R1、SECP521R1等 5、KeyShare:密钥协商时交换的公钥 每一个SupportedGroup 需要有对应的 KeyShare。 Extension Golang实现Extension 基本是才有 Request/Response 方式通讯,客户端发送Request、服务器端通过Response 的方式回复所选。 type Extension struct { extensionType ExtensionType length uint16 extensionData syntax.Vector } 下面将列出SupportedVersions 和 KeyShare 的golang 实现,其他Extension 的实现比较相似。 ClientHello 的SupportedVersions 比较简单,只要包含一个ProtocolVersions数组即可。 type SupportedVersions struct { length uint8 // byte count of array protocolVersions protocolVersions []tls.ProtocolVersion } func generateSupportedVersions(protocolVersions ...tls.ProtocolVersion) SupportedVersions { return SupportedVersions{ length: uint8(len(protocolVersions) * 2),protocolVersions: protocolVersions,} } //NewSupportedVersionsExtension create a supported versions extension func NewSupportedVersionsExtension(protocolVersions ...tls.ProtocolVersion) Extension { sv := generateSupportedVersions(protocolVersions...) return generateExtension(supportedVersions,&sv) } 本文将以ECC 的P-256、P-384 、P-521曲线 作为实现,说明如果生成对应的KeyShare type KeyShareEntry struct { group NamedGroup length uint16 keyExchange []byte } func generateKeyShareEntry(group NamedGroup) (KeyShareEntry,[]byte) { var curve elliptic.Curve switch group { case SECP256R1: curve = elliptic.P256() break case SECP384R1: curve = elliptic.P384() break case SECP521R1: curve = elliptic.P521() break } priv,x,y,err := elliptic.GenerateKey(curve,rand.Reader) if err != nil { return KeyShareEntry{},nil } nu := generateUncompressedPointRepresentation(x.Bytes(),y.Bytes()) buffer := new(bytes.Buffer) nu.Encode(buffer) ks := KeyShareEntry{ group: group,length: uint16(len(nu.X)+len(nu.Y)) + 1,keyExchange: buffer.Bytes(),} return ks,priv } type KeyShareClientHello struct { length uint16 clientShares []KeyShareEntry } func generateKeyShareClientHello(enters ...KeyShareEntry) KeyShareClientHello { var l uint16 for _,k := range enters { l += k.length + 4 } return KeyShareClientHello{ length: l,clientShares: enters,} } func NewKeyShareClientExtension(groups ...NamedGroup) (Extension,[][]byte) { keyShareList := make([]KeyShareEntry,len(groups)) privateList := make([][]byte,len(groups)) for i,g := range groups { ks,priv := generateKeyShareEntry(g) keyShareList[i] = ks privateList[i] = priv } kscl := generateKeyShareClientHello(keyShareList...) return generateExtension(keyShare,&kscl),privateList } type UncompressedPointRepresentation struct { legacyForm uint8 X []byte Y []byte } func generateUncompressedPointRepresentation(x,y []byte) UncompressedPointRepresentation { return UncompressedPointRepresentation{ legacyForm: 4,X: x,Y: y,} } 发送ClientHello组合上面的各种类型,构建ClientHello,编码后发送给远端服务器(真实存在的站点),TLS1.3 采用的是大端字节序。 func firstClientHello(conn net.Conn,host string) { supportedVersion := extension.NewSupportedVersionsExtension(tls.TLS1_3) supportedGroup := extension.NewSupportedGroupExtension(extension.SECP256R1,extension.SECP384R1) keyShare,_ := extension.NewKeyShareClientExtension(extension.SECP256R1,extension.SECP384R1) signatureScheme := extension.NewSignatureSchemeExtension(extension.ECDSA_SECP256R1_SHA256,extension.ECDSA_SECP384R1_SHA384) serverName := extension.NewServerNameExtension(host) clientHelloHandshake := handshake.NewClientHelloHandshake([]handshake.CipherSuite{ handshake.TLS_AES_128_GCM_SHA256,handshake.TLS_AES_128_CCM_SHA256,handshake.TLS_AES_256_GCM_SHA384},serverName,supportedVersion,signatureScheme,supportedGroup,keyShare) clientHelloRecord := tls.NewHandshakeRecord(&clientHelloHandshake) clientHelloRecord.Encode(outputBuffer) _,err := outputBuffer.WriteTo(conn) if err != nil { fmt.Println(err) return } } 查看Wireshark的 抓包数据 Server端的回复: 读懂TLS1.3的数据结构全英文的TLS1.3 RFC阅读起来很吃力。 为了能较好的理解协议(以及其引用的一系列RFC )需要先了解其标记语言 Presentation LanguageTLS1.3 定义了一些Presentation Language 来描述数据的结构和序列化方式。 1、类型的别名 T T' type ProtocolVersion uint16 2、定长数组类型 T T'[n] 3、可变长度数组类型 T T' 在golang 中可以这样表示 type CipherSuiteVector struct { length uint16 cipherSuites []CipherSuite } 4、枚举类型 enum { e1(v1),e2(v2),...,en(vn) [[,(n)]] } Te; enum { client_hello(1),server_hello(2),new_session_ticket(4),end_of_early_data(5),encrypted_extensions(8),certificate(11),certificate_request(13),certificate_verify(15),finished(20),key_update(24),message_hash(254),(255) } HandshakeType; HandshakeType 类型 占一个byte 2^8 // HandshakeType alies type HandshakeType byte const ( clientHello HandshakeType = 1 serverHello HandshakeType = 2 newSessionTicket HandshakeType = 4 endOfEarlyData HandshakeType = 5 encryptedExtensions HandshakeType = 8 certificate HandshakeType = 11 certificateRequest HandshakeType = 13 certificateVerify HandshakeType = 15 finished HandshakeType = 20 keyUpdate HandshakeType = 24 messageHash HandshakeType = 254 ) 5、常量表示 struct { T1 f1 = 8; /* T.f1 must always be 8 */ T2 f2; } T; 这里 T.f1 的值固定为8 struct { ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */ Random random; opaque legacy_session_id<0..32>; CipherSuite cipher_suites<2..2^16-2>; opaque legacy_compression_methods<1..2^8-1>; Extension extensions<8..2^16-1>; } ClientHello; ClientHello.legacy_version 的值固定为 0x0303 ,为了向下兼容。 6、变量定义 struct { T1 f1; T2 f2; .... Tn fn; select (E) { case e1: Te1 [[fe1]]; case e2: Te2 [[fe2]]; .... case en: Ten [[fen]]; }; } Tv; Tv.E 是变量,跟进具体的情况取值。 struct { select (Handshake.msg_type) { case client_hello: ProtocolVersion versions<2..254>; case server_hello: /* and HelloRetryRequest */ ProtocolVersion selected_version; }; } SupportedVersions; 这里 如果是 在ClientHello 里 SupportedVersions 则是一个 Vector 类型,而在ServerHello 里则是 一个ProtocolVersion (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |