SSL/TLS中的握手协议

本文是对HTTP—TCP/IP—SOCKET理解及浅析的补充,如有需要,请查看上篇文章。

SSL与TLS

2345678.jpeg

SSL协议由Netscape公司开发,历史可以追溯到Netscape Navigator浏览器统治互联网的时代。

1996年5月, TLS工作组成立,开始将SSL从Netscape迁移至IETF。由于Microsoft和Netscape当时正在为Web的统治权争得不可开交,整个迁移过程进行得非常缓慢、艰难。最终, TLS 1.0于1999年1月问世,见RFC 2246。

尽管与SSL 3相比,版本修改并不大,但是为了取悦Microsoft,协议还是进行了更名😂

2006年4月,下一个版本TLS 1.1才问世,仅仅修复了一些关键的安全问题。然而,协议的重要更改是作为TLS扩展于2003年6月发布的,并被集成到了协议中,这比大家的预期早了好几年。
2008年8月, TLS 1.2发布。该版本添加了对已验证加密的支持,并且基本上删除了协议说明中所有硬编码的安全基元,使协议完全弹性化。

SSL和TLS都是加密协议,旨在基于不安全的基础设施提供安全通信。

这意味着,如果正确部署这些协议,你就可以对互联网上的任意一个服务打开通信信道,并且可以确信你会与正确的服务器通信,安全地交换信息(你的数据不会被他人截取,而且在接收时会保持原样)。这些协议保护着通信链路即传输层,这也是TLS名称的由来。
安全不是TLS的唯一目标。 TLS实际上有以下四个主要目标(按优先顺序排列)。

  • 加密安全
    这是主要问题:为任意愿意交换信息的双方启用安全通信。
  • 互操作性
    独立的编程人员应该能够使用通用的加密参数开发程序和库,使它们可以相互通信。
  • 可扩展性
    你很快就会看到, TLS是一种能高效开发和部署加密协议的框架。其重要目标是独立于实
    际使用的加密基元(例如密码和散列函数),从而不需要创建新的协议,就允许从一个基
    元迁移到另一个。
  • 效率
    最终的目标是在实现上述所有目标的基础上保持性能成本在可接受的范围内。这需要尽
    量减少昂贵的加密操作的执行次数,并提供一个会话缓存方案,以避免这些加密操作在
    随后的连接中被执行。

互联网的核心是建立在IP( internet protocol)和TCP( transmission control protocol)协议之上的,这些协议用于将数据分割成小数据包进行传输。IP和TCP不是唯一易受攻击的协议,还有一系列其他路由协议用于协助发现网络上的其他计算机。

如果部署了加密,攻击者也许有能力得到加密数据的访问权限,但是不能解密数据或者篡改数据。为了避免伪装攻击, SSL和TLS依赖另外一项被称为公钥基础设施( public key infrastructure,PKI)的重要技术,确保将流量发送到正确的接收端。

为了理解SSL和TLS的运作,我们需要从描述网络通信的理论模型入手,即开放系统互联( open systems interconnection, OSI)模型,参见表1-1。简单来说,所有功能都被映射到七个层上。最底层是最接近物理通信链路的层,后面的层依次建立在其他层之上,提供更高级别的抽象。最顶层就是应用层,携带着应用数据。

pic_002.png

以这种方式安排通信可以清晰地划分概念:高层的协议不必担心在底层实现的功能。进一步说,不同层次的协议可以加入通信或者从通信中删除,一种底层协议可以服务于多种上层协议SSL和TLS是这一原则如何在实践中运用的一个重要示例。它用于TCP协议之上,上层协议(如HTTP)之下。当不需要加密时,可以将TLS从模型中去掉,这并不会对上层协议产生影响(它们将直接与TCP协同工作)。当需要加密时,就可以利用TLS加密HTTP,以及其他TCP协议(比如SMTP、 IMAP等)。

TLS中的握手协议

握手是TLS协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换6~10条消息。根据配置和支持的协议扩展的不同,交换过程可能有许多变种。

在使用中经常可以观察到以下三种流程:

  • (1) 完整的握手,对服务器进行身份验证;

  • (2) 恢复之前的会话采用的简短握手;

  • (3) 对客户端和服务器都进行身份验证的握手。

    握手协议消息的标头信息包含消息类型( 1字节)和长度( 3字节),余下的信息则取决于消息类型:

struct {
    HandshakeType msg_type;
    uint24 length;
    HandshakeMessage message;
} Handshake;

2.2.1 完整的握手
每一个TLS连接都会以握手开始。如果客户端此前并未与服务器建立会话,那么双方会执行一次完整的握手流程来协商TLS会话。握手过程中,客户端和服务器将进行以下四个主要步骤。

  • (1) 交换各自支持的功能,对需要的连接参数达成一致。
  • (2) 验证出示的证书,或使用其他方式进行身份验证。
  • (3) 对将用于保护会话的共享主密钥达成一致。
  • (4) 验证握手消息并未被第三方团体修改。

在实际使用中,第2步和第3步都是密钥交换(更通用的说法是密钥生成)的一部分,
密钥交换是一个单独的步骤。我更喜欢将它们分开来说,用以强调协议的安全性取决
于正确的身份验证。身份验证有效地在TLS的外层工作。如果没有身份验证,主动攻
击者就可以将自身嵌入会话,并冒充会话的另一端。

本节会讨论最常见的TLS握手流程,就是一种在不需要身份验证的客户端与需要身份验证的服务器之间的握手,如图2-2所示。

009876_003.png
  • (1) 客户端开始新的握手,并将自身支持的功能提交给服务器。

  • (2) 服务器选择连接参数。

  • (3) 服务器发送其证书链(仅当需要服务器身份验证时)。

  • (4) 根据选择的密钥交换方式,服务器发送生成主密钥的额外信息。

  • (5) 服务器通知自己完成了协商过程。

  • (6) 客户端发送生成主密钥所需的额外信息。

  • (7) 客户端切换加密方式并通知服务器。

  • (8) 客户端计算发送和接收到的握手消息的MAC并发送。

  • (9) 服务器切换加密方式并通知客户端。

  • (10) 服务器计算发送和接收到的握手消息的MAC并发送。

    假设没有出现错误,到这一步,连接就建立起来了,可以开始发送应用数据。现在让我们了
    解一下这些握手消息的更多细节。

  1. ClientHello
    在一次新的握手流程中, ClientHello消息总是第一条消息。这条消息将客户端的功能和首选项传送给服务器。客户端会在新建连接后,希望重新协商或者响应服务器发起的重新协商请求(由HelloRequest消息指示)时,发送这条消息。在下面的例子中,你可以观察到ClientHello消息。为了更简洁,我减少了一些信息展示,但是包含了所有的关键元素。
Handshake protocol: ClientHello
    Version: TLS 1.2
    Random
        Client time: May 22, 2030 02:43:46 GMT
        Random bytes: b76b0e61829557eb4c611adfd2d36eb232dc1332fe29802e321ee871
    Session ID: (empty)
    Cipher Suites
        Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
        Suite: TLS_RSA_WITH_AES_128_GCM_SHA256
        Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
        Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA
        Suite: TLS_RSA_WITH_AES_128_CBC_SHA
        Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA
        Suite: TLS_RSA_WITH_RC4_128_SHA
    Compression methods
        Method: null
    Extensions
        Extension: server_name
            Hostname: www.feistyduck.com
        Extension: renegotiation_info
        Extension: elliptic_curves
            Named curve: secp256r1
            Named curve: secp384r1
        Extension: signature_algorithms
            Algorithm: sha1/rsa
            Algorithm: sha256/rsa
            Algorithm: sha1/ecdsa
            Algorithm: sha256/ecdsa

可以看到,绝大多数消息字段光看名称就很容易理解,而且消息的结构也很容易理解。

  • 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)块由任意数量的扩展组成。这些扩展会携带额外数据。我会在本章后面对最常见的扩展进行讨论。
  1. ServerHello
    ServerHello消息的意义是将服务器选择的连接参数传送回客户端。这个消息的结构与ClientHello类似,只是每个字段只包含一个选项。
Handshake protocol: ServerHello
    Version: TLS 1.2
    Random
        Server time: Mar 10, 2059 02:35:57 GMT
        Random bytes: 8469b09b480c1978182ce1b59290487609f41132312ca22aacaf5012
    Session ID: 4cae75c91cf5adf55f93c9fb5dd36d19903b1182029af3d527b7a42ef1c32c80
    Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    Compression method: null
    Extensions
        Extension: server_name
        Extension: renegotiation_info

服务器无需支持客户端支持的最佳版本。如果服务器不支持与客户端相同的版本,可以提供某个其他版本以期待客户端能够接受。

  1. Certificate
    典型的Certificate消息用于携带服务器X.509证书链。<u>证书链是以ASN.1 DER编码的一系列证书,一个接着一个组合而成。</u>主证书必须第一个发送,中间证书按照正确的顺序跟在主证书之后。根证书可以并且应该省略掉,因为在这个场景中它没有用处。服务器必须保证它发送的证书与选择的算法套件一致。

比方说,公钥算法与套件中使用的必须匹配。除此以外,一些密钥交换算法依赖嵌入证书的特定数据,而且要求证书必须以客户端支持的算法签名。所有这些都表明服务器需要配置多个证书(每个证书可能会配备不同的证书链,Certificate消息是可选的,因为并非所有套件都使用身份验证,也并非所有身份验证方法都需要证书。

更进一步说,虽然消息默认使用X.509证书,但是也可以携带其他形式的标志;一些套件就依赖PGP密钥。

  1. ServerKeyExchange
    ServerKeyExchange消息的目的是携带密钥交换的额外数据。消息内容对于不同的协商算法套件都会存在差异。在某些场景中,服务器不需要发送任何内容,这意味着在这些场景中根本不会发送ServerKeyExchange消息。

  2. ServerHelloDone
    ServerHelloDone消息表明服务器已经将所有预计的握手消息发送完毕。在此之后,服务器会等待客户端发送消息。

  3. ClientKeyExchange
    ClientKeyExchange消息携带客户端为密钥交换提供的所有信息。这个消息受协商的密码套件的影响,内容随着不同的协商密码套件而不同。

  4. ChangeCipherSpec
    ChangeCipherSpec消息表明发送端已取得用以生成连接参数的足够信息,已生成加密密钥,并且将切换到加密模式。客户端和服务器在条件成熟时都会发送这个消息。

注意ChangeCipherSpec不属于握手消息,它是另一种协议,只有一条消息,作为它的子协议进行实现。这个设计的结果是这条消息不是握手完整性验证算法的一部分,这使得正确实现TLS更为困难。

在2014年6月,人们发现OpenSSL对于ChangeCipherSpec消息的处理不正确,使得OpenSSL为主动网络攻击敞开了大门。同样的问题也出现在其他所有子协议中。主动网络攻击者利用缓冲机制在首次握手时发送未经验证的警报消息,更可以在开始加密以后破环真正的警报消息。为了避免更严重的问题,应用数据协议消息必须等到首次握手完成以后才能开始发送。

  1. Finished
    Finished消息意味着握手已经完成。消息内容将加密,以便双方可以安全地交换验证整个握手完整性所需的数据。这个消息包含verify_data字段,它的值是握手过程中所有消息的散列值。

这些消息在连接两端都按照各自所见的顺序排列,并以协商新得到的主密钥计算散列。这个过程是通过一个伪随机函数( pseudorandom function, PRF)来完成的,这个函数可以生成任意数量的伪随机数据。我将在本章的后续部分中对其进行介绍。散列函数与PRF一致,除非协商的套件指定使用其他算法。

两端的计算方法一致,但会使用不同的标签:客户端使用client finished,而服务器则使用serverfinished。verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))

因为Finished消息是加密的,并且它们的完整性由协商MAC算法保证,所以主动网络攻击者不能改变握手消息并对vertify_data的值造假。
理论上攻击者也可以尝试找到一组伪造的握手消息,得到的值与真正消息计算出的verity_data的值完全一致。这种攻击本身就非常不容易,而且因为散列中混入了主密钥(攻击者不知道主密钥),所以攻击者根本不会尝试。

在TLS 1.2版本中, Finished消息的长度默认是12字节( 96位),并且允许密码套件使用更长的
长度。在此之前的版本,除了SSL 3使用36字节的定长消息,其他版本都使用12字节的定长消息。

客户端身份验证

尽管可以选择对任意一端进行身份验证,但人们几乎都启用了对服务器的身份验证。

如果服务器选择的套件不是匿名的,那么就需要在Certificate消息中跟上自己的证书。相比之下,服务器通过发送CertificateRequest消息请求对客户端进行身份验证。消息中列出所有可接受的客户端证书。作为响应,客户端发送自己的Certificate消息(使用与服务器发送证书相同的格式),并附上证书。此后,客户端发CertificateVerify消息,证明自己拥有对应的私钥。

完整的握手如图2-3所示。

096534234004.png

只有已经过身份验证的服务器才被允许请求客户端身份验证。基于这个原因,这个选项被称
为相互身份验证( mutual authentication)。

  1. CertificateRequest
    服务器使用CertificateRequest消息请求对客户端进行身份验证,并将其接受的证书的公钥和签名算法传送给客户端。它也可以选择发送一份自己接受的证书颁发机构列表,这些机构都用其可分辨名称来表示:
struct {
  ClientCertificateType certificate_types;
  SignatureAndHashAlgorithm supported_signature_algorithms;
  DistinguishedName certificate_authorities;
} CertificateRequest;
  1. CertificateVerify
    客户端使用CertificateVerify消息证明自己拥有的私钥与之前发送的客户端证书中的公钥相对应。消息中包含一条到这一步为止的所有握手消息的签名:
struct {
  Signature handshake_messages_signature;
} CertificateVerify;

会话恢复

完整的握手协议非常复杂,需要很多握手消息和两次网络往返才能开始发送客户端应用数
据。此外,握手执行的密钥学操作通常需要密集的CPU处理。身份验证通常以客户端和服务器证
书验证(以及证书吊销检查)的形式完成,需要更多的工作。这其中的许多消耗都可以通过简短
握手的方式节约下来。

9087865_005.png

最初的会话恢复机制是,在一次完整协商的连接断开时,客户端和服务器都会将会话的安全参数保存一段时间。

希望使用会话恢复的服务器为会话指定唯一的标识,称为会话ID。服务器在 ServerHello消息中将会话ID发回客户端(请参见2.2.2节中的示例)。

希望恢复早先会话的客户端将适当的会话ID放入ClientHello消息,然后提交。服务器如果 愿意恢复会话,就将相同的会话ID放入ServerHello消息返回,接着使用之前协商的主密钥生成 一套新的密钥,再切换到加密模式,发送Finished消息。客户端收到会话已恢复的消息以后,也 进行相同的操作。这样的结果是握手只需要一次网络往返。简短握手如图2-4所示。图2-4 简短握手,用于恢复已经建立的会话用来替代服务器会话缓存和恢复的方案是使用会话票证( sesession ticket)。它是2006年引入 的(参见RFC 4507),随后在2008年进行了更新(参见RFC 5077)。使用这种方式,除了所有的状态都保持在客户端(与HTTP Cookie的原理类似)之外,其消息流与服务器会话缓存是一样的。

此文为18年读《HTTPS权威指南》记录内容。如有错误,请批评指正。