前言
5G 未来
网络互连后信息的安全传输,也是不容忽视的问题。TLS1.3 相对于之前的版本,修正了很多安全陷阱,降低了握手的次数,提高了效率。
私有化
下面我们将尽可能的参照TLS1.3定义的结构编写代码,同时利用Wireshark 抓包查看包情况。
从ClientHello开始
先看结果
上图是Wireshark 的抓包结果,ali-BF 是我本机电脑,Server是某台启用TLS1.3的网络服务器。
从图中可以看到三次tcp 握手后, 我们发出ClientHello 消息长度348个字节,经过一个ack后成功收到 Server Hello 消息。
编码实现
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
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协议,因此在实现上并不是很关注性能和效率问题及部分异常情况。
Handshake
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,
}
}
ClientHello
ClientHello 对应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
}
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)
}
各种Extension
ClientHello 主要是通过Extension 传递密钥协商必要的素材, 第一次ClientHello 至少需要包含以下5个Extension:
1、ServerName : 所请求的主机名
这样做有个缺点,ClientHello 是明文传输,中间人可以明确探知该流量的目的地。像WhatApp 和 Signal 就采用一种叫“域前置” 的技术去绕过该问题。
2、SupportedVersions :所能支持的TLS 版本如:TLS1.1、TLS1.2、TLS1.3等
用于协商最终采用的TLS 版本,在ClientHello 所能支持的列表,把最优先支持的放在第一位。
3、SignatureAlgorithms : 所支持的签名算法
如:ECDSA_SECP256R1_SHA256、ECDSA_SECP384R1_SHA384等
本文主要关注椭圆曲线 如:SECP256R1、SECP384R1、SECP521R1等
每一个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的 抓包数据
包含了我们所构建的数据和几个Extenison
读懂TLS1.3的数据结构
全英文的 TLS1.3 RFC 阅读起来很吃力。 为了能较好的理解协议(以及其引用的一系列RFC )需要先了解其标记语言
Presentation Language
TLS1.3 定义了一些Presentation Language 来描述数据的结构和序列化方式。
type ProtocolVersion uint16
type CipherSuiteVector struct {
length uint16
cipherSuites []CipherSuite
}
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
在golang 中可以这样定义
// 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、常量表示
在TLS 1.3 中协议中有些字段必须设置为固定值,主要是为了兼容旧版本,因此需要定义常量的表示。
struct {
T1 f1 = 8; /* T.f1 must always be 8 */
T2 f2;
} T;
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 ,为了向下兼容。
struct {
T1 f1;
T2 f2;
....
Tn fn;
select (E) {
case e1: Te1 [[fe1]];
case e2: Te2 [[fe2]];
....
case en: Ten [[fen]];
};
} Tv;
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