前言

撰写本文时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建立一个安全高效的 “安全会话层”,
1、往下可以增加连接管理模块、心跳控制模块,如:实现多级TCP 通道快速重连重发等,解决弱网状态下的各种问题。
2、往上可以服务各种不同的网络类型如p2p或经典C/S,
3、更上层可以服务一些中间件如: RPC 、MQTT等支撑业务。

下面我们将尽可能的参照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 byteconst (invalid          ContentType = 0changeCipherSpec ContentType = 20alert            ContentType = 21handshake        ContentType = 22applicationData  ContentType = 23
)// TLSPlaintext plaintext on record layer
type TLSPlaintext struct {contentType         ContentTypelegacyRecordVersion ProtocolVersion //staticlength              uint16fragment            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 ObjectByteCount() 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协议,因此在实现上并不是很关注性能和效率问题及部分异常情况。

Handshake

TLS1.3 支持三种方式的密钥协商:PSK-Only、(EC)DHE, 和PSK with (EC)DHE ,本文主要是关注 ECDHE。
Handshake 对应了TLSPlaintext 的fragment ,因为其实现了Vector 接口


type Handshake struct {msgType       HandshakeType /* handshake type */length        uint24handshakeData 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     uint16extensions []extension.Extension
}type ClientHello struct {legacyVersion            tls.ProtocolVersionrandom                   [32]bytelegacySessionId          legacySessionIdcipherSuites             CipherSuiteVectorlegacyCompressionMethods legacyCompressionMethodsextensions               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]byterand.Read(r[:])extBytes := 0for _, 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 := 0for _, 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 : 所请求的主机名

通常情况下一台服务器会寄宿多个站点,即同一个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 ExtensionTypelength        uint16extensionData syntax.Vector
}

下面将列出SupportedVersions 和 KeyShare 的golang 实现,其他Extension 的实现比较相似。

ClientHello 的SupportedVersions 比较简单,只要包含一个ProtocolVersions数组即可。

type SupportedVersions struct {length           uint8 // byte count of array protocolVersionsprotocolVersions []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       NamedGrouplength      uint16keyExchange []byte
}func generateKeyShareEntry(group NamedGroup) (KeyShareEntry, []byte) {var curve elliptic.Curveswitch group {case SECP256R1:curve = elliptic.P256()breakcase SECP384R1:curve = elliptic.P384()breakcase 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       uint16clientShares []KeyShareEntry
}func generateKeyShareClientHello(enters ...KeyShareEntry) KeyShareClientHello {var l uint16for _, 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] = ksprivateList[i] = priv}kscl := generateKeyShareClientHello(keyShareList...)return generateExtension(keyShare, &kscl), privateList}type UncompressedPointRepresentation struct {legacyForm uint8X          []byteY          []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

Server端的回复:

可以看到 Server 选择了 AES_256_GCM_SHA384 加密套件、KeyShare 选择了 p-256曲线。同时发现服务器端已开始传输部分加密的数据 “ApplicationData” 。


读懂TLS1.3的数据结构

全英文的 TLS1.3 RFC 阅读起来很吃力。 为了能较好的理解协议(以及其引用的一系列RFC )需要先了解其标记语言

Presentation Language

TLS1.3 定义了一些Presentation Language 来描述数据的结构和序列化方式。

1、类型的别名 T T'
T' 为T的类型别名
如:uint16 ProtocolVersion , ProtocolVersion 就是 uint16 的别名,它表明ProtocolVersion在传输中占有2个字节。golang 中的定义:

type ProtocolVersion uint16

2、定长数组类型 T T'[n]
T'[] 表示为T的数组,需要注意的是n并不代表T的个数,而是T'类型占用多少个byte
如: opaque Random[32] ,表示 Random 类型占用了32 个字节。opaque 表示不透明的数据结构,可以理解为byte数组

3、可变长度数组类型 T T'
T'<> 包含两部分: head+body,
head的值表示body 占用了多少个字节,
body的值为真实负载,即T的数组。 head 本身所占的字节数由 决定。
如:
CipherSuite cipher_suites<2..2^16-2>
表示 CipherSuite 类型的数组,其head 占用 2byte, uint16可以容下2^16个字节。

在golang 中可以这样表示

type CipherSuiteVector struct {length       uint16cipherSuites []CipherSuite
}

4、枚举类型 enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;
e1表示枚举类型的值。最后的 [[,(n)]] 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
在golang 中可以这样定义

// HandshakeType alies
type HandshakeType byteconst (clientHello         HandshakeType = 1serverHello         HandshakeType = 2newSessionTicket    HandshakeType = 4endOfEarlyData      HandshakeType = 5encryptedExtensions HandshakeType = 8certificate         HandshakeType = 11certificateRequest  HandshakeType = 13certificateVerify   HandshakeType = 15finished            HandshakeType = 20keyUpdate           HandshakeType = 24messageHash         HandshakeType = 254
)

5、常量表示
在TLS 1.3 中协议中有些字段必须设置为固定值,主要是为了兼容旧版本,因此需要定义常量的表示。

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

TLS1.3 协议的Golang 实现——ClientHello相关推荐

  1. 安全修复之Web——【中危】启用了不安全的TLS1.0、TLS1.1协议

    安全修复之Web--[中危]启用了不安全的TLS1.0.TLS1.1协议 背景 日常我们开发时,会遇到各种各样的奇奇怪怪的问题(踩坑o(╯□╰)o),这个常见问题系列就是我日常遇到的一些问题的记录文章 ...

  2. TLSNotary中心化预言机(1) TLS1.1协议

    TLSNotary是基于TLS1.1协议的流程来实现 1.TLS1.1协议 协议的大体流程是协商密钥生成算法然后通过随机数生成秘钥,最终双向验证秘钥达成握手,详细流程可以参见文末链接. 协议最终的效果 ...

  3. http服务(nginx、apache)停用不安全的SSL协议、TLS1.0和TLS1.1协议/启用TLS1.3

    文章目录 一.http服务停用不安全的TLS1.0和TLS1.1协议 nginx Apache apache要支持TLS1.2 版本要求 工作中遇到问题整理 [error] No SSL protoc ...

  4. TLS1.3 协议的加密过程

    记录一下 TLS1.3 的加密过程,如何协商会话秘钥 TLS 就是 HTTPS 的那一层加密协议,前身是SSL,但是现在SSL已经淘汰了.之前用wireshark 抓包看,现在的网站多是TLS1.2. ...

  5. tls1.2协议学习笔记

    一.概述 从技术上讲,TLS 1.0与SSL 3.0的差异非常微小 TLS:Transport Layer Security SSL:Secure Socket Layer TLS/SSL 协议位于应 ...

  6. TLS1.3 协议介绍

    一. 协议介绍 TLS(Transport Layer Security )的中文全称是安全传输层协议,TLS的设计目标是在传输层之上,构建一个安全传输层. 因为有了TLS的存在,HTTP协议栈增加了 ...

  7. 【中危】启用了不安全的TLS1.0、TLS1.1协议

    文章目录 1. 漏洞描述 2. 补充学习 3. 漏洞检测 4. 漏洞修复 5. 片尾彩蛋 1. 漏洞描述 TLS 1.0是于1996年发布的老版协议,使用的是弱加密算法和系统.比如SHA-1和MD5, ...

  8. java6不支持tlsv1.2_解决 JDK1.7 不支持 VCenter 6.7 的问题(涉及到Https TLS1.2协议)

    解决 JDK1.7 不支持 VCenter 6.7 的问题 问题描述 原项目工程是使用JDK 1.7,可以连接 5.X版本和 6.0版本的 VCenter资源池. 但是,现在VCenter已经升到 6 ...

  9. 5月11日云栖精选夜读丨清华大学成功卫冕ASC18世界超算总决赛冠军,黑马上海科大斩获AI大奖

    原文链接:点击打开链接 摘要: 5月9日,2018 ASC世界大学生超级计算机竞赛(ASC18)总决赛在南昌大学落下帷幕,清华大学成功卫冕总冠军,首次入围总决赛的"黑马"上海科技大 ...

最新文章

  1. AAAI-19 日程 安排
  2. Java面试笔试题大汇总一(最全+详细答案)
  3. 《Java核心技术 卷Ⅱ 高级特性(原书第10版)》一导读
  4. hdu 1593(数学)
  5. 样式和主题的区别(Styles and Themes)
  6. matlab求灰度图像梯度,[求助]如何求图像的梯度
  7. 为什么互联网35岁是道坎?
  8. 开发WinRT自定义组件之富文本框
  9. 行车记录仪数据集_福特自动驾驶数据集公布 总体积达1.6TB或为数据最全的数据集之一...
  10. 找出最耗资源的sql ----没明白
  11. 推荐一些2021年整理的PHP毕业设计、毕设参考作品案例
  12. 贴片铝电解电容封装的说明
  13. DDA算法和Bresenham算法
  14. PHP探测手机客户端
  15. 如何关闭方正软件保护卡
  16. 网站视频很卡怎么办?有没有免费的视频平台?使用阿里云OSS对象云存储+下行流量包解决网站文件/视频访问慢问题
  17. 编程猫海龟编辑器 附使用教程
  18. 计算机作文600字关于科学事业,对科学事业的执着追求作文600字
  19. 无刷电机噪音产生原因及解决方法
  20. Mapper method 'dao.xxx' has an unsupported return type

热门文章

  1. 拿下丰厚的年终奖,却未能拯救总薪酬,2021 年度 IT 薪酬调查报告出炉!
  2. 抗住 8 亿人买买买!双 11 背后黑科技大曝光
  3. Hadoop 之父趣事:用儿子的大象玩偶为大数据项目命名
  4. 程序员饭碗不保了?GPT-3 最强应用发布,动动手指就自动写代码的神器来了!...
  5. String字符串位置移动
  6. mysql优化-----多列索引的左前缀规则
  7. DataGridView使用技巧十一:DataGridView用户输入时,单元格输入值的设定
  8. ORACLE的impdp和expdp命令
  9. 云栖小镇不是“镇”,就像中关村不是“村”。小镇是一个符号,就像起建于50年前的硅谷的“谷”,和100年前爱迪生所在的门洛公园。...
  10. Kafka设计解析(二):Kafka High Availability (上)