2.1 记录协议
宏观上,TLS以记录协议(record protocol)实现。记录协议负责在传输连接上交换所有底层消息,并可以配置加密。每一条TLS记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度。消息数据紧跟在标头之后。每一个TLS记录指定有唯一的64位序列号,但不会在线路上传输。任一端都有自身的序列号并跟踪来自另一端记录的数量。这些值是对抗重放攻击的一部分。
记录协议从多个重要的宏观角度对通信进行考量,是一个很有用的协议抽象。
- 消息传输
记录协议传输有其他协议层提交给它的不透明数据缓冲区。如果缓冲区超过记录的长度限制(16384字节),记录协议会将其切分成更小的片段。反过来也是有可能的,属于同一个子协议的小缓冲区也可以组合成一个单独的记录。 - 加密以及完整性验证
在一个刚建立起来的连接上,最初的消息传输没有受到任何保护(从技术上讲,就是使用了TLS_NULL_WITH_NULL_NULL密码套件)。这是必需的,否则第一次协商就无法进行。但是,一旦握手完成,记录层就开始按照协商取得的连接参数进行加密和完整性验证。 - 压缩
理论上,在加密之前透明地进行压缩非常好,可是在实践中几乎没有人重要做。这主要是因为每个应用在HTTP层就以及对它们的出口流量进行过压缩。这个特性在2012年遭受过一次严重打击,当时CRMIE攻击使压缩成为最不安全的特性,所以限制也不会再被使用。 - 扩展性
记录协议只关注传输和加密,而将所有其他特性转交给子协议。这个方法使TLS可以扩展,因为可以很方便的添加子协议。伴随着记录协议而被加密,所有子协议都会以协商取得的连接参数自动得到保护。
TLS的主要规格说明书定义了四个核心子协议:握手协议(handshake protocol)、密钥规格变更协议(change cipher spec protoco)、应用数据协议(application data protocol)和警报协议(alter protocol)。
2.2.1 完整的握手
每一个TLS连接都会以握手开始。如果客户端此前并未与服务器建立会话,那么双方会执行一次完整的握手流出来协商TLS会话。握手过程中,客户端和服务器将进行一下四个主要步骤。
(1)交换各自支持的功能,对需要的连接参数达成一致。
(2)验证出示的证书,或使用其他方式进行身份验证。
(3)对将用于保护会话的共享密钥达成一致。
(4)验证握手消息并未被第三方团体修改。
- ClienHello
在一次新的握手流程中,ClientHello消息总是第一条消息。这条消息将客户端的功能和首选项传送给服务器。客户端会在新建连接后,希望重新协商或者响应服务器发起的重新协商请求(由HelloRequest消息指示)时,发送这条消息。
ClientHello消息结构:
- Version
协议版本(protocol version)指示客户端支持的最佳协议版本。 - Random
随机送(random)字段包含32字节的数据。当然,只有28字节是随机生成的;剩余的4字节包含额外的信息,受客户端时钟影响。准确来说,客户端时间与协议不相关,而且协议规格文档言及此事也很清楚(“基本的TLS协议不需要正确设置时钟,更高层或应用协议可以定义额外的需求项。”);该字段是1994年在Netscape Navigator中发现来一个严重故障之后,为了防御弱随机数生成器而引入的。尽管这个字段曾经一直包含由精确时间的部分,但现在仍然有人担心客户端时间可能被用于大规模浏览器指纹采集,所以一些浏览器会给它们的时间添加时钟扭曲(正如你在示例中所看到的那样),或者简单地发送随机的4字节。
在握手时,客户端和服务器都会提供随机数。这种随机性对每次握手都是独一无二的,在身份验证中骑着举足轻重的作用。它可以防止重放攻击,并确认初始数据交换的完整性。 - Session ID
在第一次连接时,会话ID(Session ID)字段是空的,这表示客户端并不希望恢复某个已存在的会话。在后续的连接中,这个字段可以保持会话的唯一标识。服务器可以借助会话ID在自己的缓存中找到对应的会话状态。典型的会话ID包含32自己随机生成的数据,这些数据本身没有什么价值。 - Cipher Suites
密码套件(cipher suite)块是由客户端支持的所有密码套件组成的列表,该列表是按优先级顺序排列的。 - Compression
客户端可以提交一个或多个支持压缩的方法。默认的压缩是null,代表没有压缩。 - Extensions
扩展(extension)块是由任意数量的扩展组成。这些扩展会携带额外数据。我会在本章节后面对最常见的扩展进行讨论。
- ServerHello
ServerHello消息的意义是将服务器选择的连接参数传送客户端。这个消息的结构与ClientHello类型,只是将每个字段包含一个选项。
服务器无需支持客户端支持的最佳版本。如果服务器不支持与客户端相同的版本,可以提供某个其他版本以期客户端能够接受。 - Certificate
典型的Certificate消息用于携带服务器X.509证书链证书链是以ASN.1DER编码的一系列证书,一个接着一个组合而成。主证书必须第一个发送,中间证书按照正确的顺序跟在主证书之后。根证书可以并且应该省略,因为在这个场景中它没有用处。
服务器必须保证它发送的证书与选择的算法套件一致。比方说,公钥算法与套件中使用的必须匹配。除此之外,一些密钥交换算法依赖嵌入证书的特定数据,而且要求证书必须以客户端支持的算法签名。所有这些都表面服务器需要配置多个证书(每个证书可能会配备不同的证书链)。
Certificate消息是可选的,因为并非所有套件都使用身份验证,也并非所有身份验证方法都需要证书。更进一步,虽然消息默认使用X.509证书,但是也可以携带其他形式的标志:一些套件就依赖PGP密钥。
4.ServerKeyExchange
ServerKeyExchange消息的目的是携带密钥交换的额外数据。消息内容对于不同的协商算法套件都会存在差异。在某些场景中,服务器不需要发送任何内容,这意味着这些场景中根本不会发送ServerKeyExchange消息。
5.ServerHelloDone
ServerHelloDone消息表明服务器以及将所有预计的握手消息发送完毕。在此之前,服务器会等待客户端发送消息。
6.ClientKeyExchange
ClientKeyExchange消息携带客户端为密钥交换提供的所有消息。这个消息受协商的密码套件的影响,内容随着不同的协商密钥套件而不同。
7.ChangeCipherSpec
ChangeCipherSpec消息表明发送端已取得用以生成连接参数的足够信息,已生成加密密钥,并且将切换到加密模式。客户端和服务端在条件成熟时都会发送这个消息。
8.Finished
Finished消息意味着握手已经完成。消息内容将加密,以便双方可以安全地交换验证整个握手完整性所需的数据。
这个消息包含verify_data字段,它的值是握手过程中所有消息的散列值。这个过程是通过一个为随机数函数(pseudorandom function,PRF)来完成,这个函数可以生成任意数量的伪随机数数据。我将在本章的后续部分中对这其进行介绍。散列函数与PRF一致,除非协商的套件指定使用其他算法。两端的计算方法一致,但会使用不同的标签:客户端使用client finished,而服务器则使用server finished。
verify_data = PRF(master_secret,finished_label,Hash(handshake_messages))
因为Finished消息是加密的,并且它们的完整性由协商MAC算法保证,所以主动网络攻击者不能改变握手消息并对verify_data的值造假。
理论上攻击者也可以尝试找到一组伪造的握手消息,得到的值与真正消息计算出的verify_data的值是完全一致。这种攻击本身就非常不容易,而且因为散列中混入来主密钥(攻击者不知道主密钥),所以攻击者根本不会尝试。
在TLS1.2版本中,Finished消息的长度默认是12字节(96位),并且允许密码套件使用更长的长度。在此之前的版本,除了SSL3使用36字节的定长消息,其他版本都使用12字节的定长消息。
2.2.2 客户端身份验证
尽管可以选择对任意一端进行身份验证,但人们几乎都启用来对服务器的身份验证。如果服务器选择的套件不是匿名的,那么就需要在Certificate消息中跟上自己的证书。
相比之下,服务器通过发送CertificateRequest消息请求对客户端进行身份验证。消息中列出所有可以接受的客户端证书。作为回应,客户端发送自己的Certificate消息(使用与服务器发送证书相同的格式),并附上证书。此后,客户端发送CertificateVerify消息,证明自己拥有对应的私钥。
只有已经过身份验证的服务器才被允许请求客户端身份验证。基于这个原因,这个选项被称为相互身份验证(mutual authentication)。
1.CertificateRequest
服务器使用CertificateRequest消息请求对客户端进行身份验证,并将其接受的证书的公钥和签名算法传送给客户端。它也可以选择发送一份自己接受的证书颁发机构列表,这些机构都用其可分辨名称来标示。
2.CertificateVerify
客户端使用CertificateVerify消息证明自己拥有的私钥与之前发送的客户端证书中的公钥相对应。消息中包含一条到这一步为止的所有握手消息的签名。
2.2.3 会话恢复
完整的握手协议非常复杂,需要很多握手消息和两次网络往返才能发送客户端应用数据。此外,握手执行的密钥学通常需要密集的CPU处理。身份验证通常需要以客户端和服务器证书验证(以及证书吊销检查)的形式完成。需要更多的工作。这其中的许多消耗都可以通过简短握手的方式节约下来。
最初的会话恢复机制是,在一次完整协商的连接断开时,客户端和服务器都会将会话的安全参数保存一段时间。希望使用会话恢复的服务器为会话指定唯一的标识,称为会话ID。服务器在Serverhello消息中将会话ID发回客户端。
希望恢复早先会话的客户端将适当的会话ID放入ClientHello消息,然后提交。服务器如果愿意恢复会话,就将相同的会话ID放入ServerHello消息返回,接着使用之前协商的主密钥生成一套新的密钥,再切换到加密模式,发送Finished消息。客户端收到会话已恢复的消息以后,也进行相同的操作。这样的结果是握手只需要进行一次网络往返。
用来替代服务器会话缓存和恢复的方案是使用会话票证(session ticket)。它是2006年引入的(参考RFC4507),随后在2008年进行来更新(参照RFC5077)。使用这种方式,除了所有的状态都保持在客户端(与HTTP Cookie的原理类似)之外,其消息与服务器会话缓存是一样的。