原文:http://blog.fourthbit.com/2014/12/23/traffic-analysis-of-an-ssl-slash-tls-session
在这篇文章中,我将会给大家展示当我们使用SSL/TLS的时候,在协议层面上发生了什么。为了便于分析,我将会使用一个基于OpenSSL实现的非阻塞TCP客户端和服务端。
我们要记住,SSL/TLS能让应用像底层基础设施(网络和主机)一样安全,确保通信安全是它唯一做的事。SSL/TLS是一个独立的协议,它处于应用层(一般来说是HTTP,其他同样也可以)和传输层(TCP)之间。如此,TLS不需要上下层协议有大的改动,对用户而言,它几乎是透明的,也就是说用户根本不需要知道这个协议的存在。当然,这也带来了一些弊端,它在一些基础方面对协议做了限制(比如不支持UDP)。
自SSL/TLS诞生以来,它已经有了相当大的改进。现在,SSL 2.0和3.0被认为是不安全的,他们已经被TLS 1.0/1.1/1.2 替换。这些协议的历史是一个很有趣的话题。
协议描述
就像上面提到的,TLS协议在应用层和传输层中间。它被分割成两个主要的子层。下面是这个协议的主要结构和它在网络协议栈中的位置。
下面的子层在TCP之上,因为TCP是一个面向连接的可靠的协议。这一层主要包括TLS记录协议。简而言之,记录协议首先将上层协议的数据变成2^14bytes或更少的块,然后对数据进行压缩(可选),加上一个消息认证码(Message Authentication Code),最后,根据协商的加密细则对数据进行加密并添加一个SSL记录头。值得注意的是,每一个块都会被打包进一个结构体,这个结构体并不保存应用层消息的边界,这就意味着多条相同类型的消息可能会被合并到一个结构体中。
下面的图描述了建立一个SSL Record的过程
上面的子层在SSL记录协议(SSL Record Protocol)之上,它由四个子协议组成。每个子协议都有非常明确的目的,并在通信的不同阶段被应用:
握手协议(HandShake Protocol):它使双方可以相互认证,以及为本次连接协商出一个加密套件(cipher suite)和其他参数。SSL握手协议涉及到客户端和服务端之间4组消息交换。每一组都是在独立的TCP段(segment)中传输。下面的图为这个过程做了总结,它包含好几个步骤,一些步骤是可选的。注意,ChangeCipherSpec不属于这个协议,他们是独立的协议,下面会讲到。
ChangeCipherSpec协议:它使之前双方协商得到的参数起作用,通信变成加密的。
- Alert协议:用于通信异常,并预警可能降低通信安全性的问题
- 应用数据协议(Application Data Protocol):它接受任意大小的数据(通常是应用层数据),将数据喂给安全通道(feeds it through the secure channel)。
多条消息可以被串联成一条记录层(Record Layer)消息,但是这些消息必须归属于相同的子协议。结果是,这4个协议的每个都必须是自定界的(比如,都需要包含自己的长度字段)。
记录协议格式(Record Protocol format)
TLS 记录头(Record header)包含3个字段,高层协议建立在它之上:
- Byte 0:TLS记录类型(TLS recode type)
- Bytes 1-2: TLS version (major/minor)
- Bytes 3-4: Length of data in the record (excluding the header itself). The maximum supported is 16384 (16K)
握手协议格式(Handshake Protocol format)
这是TLS种最复杂的子协议。TLS标准主要也在这一部分,因为它为建立一个安全的连接处理了所有细节。下面的图展示了握手协议消息的主体结构。TLS标准存在10种(不包括扩展)握手消息类型,下面一一做了展示。
- HelloRequest:允许服务器重启握手协商。不常用。如果一个连接被挂起了足够长时间只是安全性下降,服务器可以使用此消息迫使客户端重新协商新的会话秘钥(keys)。
- ClientHello:这条消息代表了一个TLS握手协商的开始。发送它的同时会带上客户端支持的加密套件列表,服务端会挑选最合适的那个(安全性最强的那个),此外还有一个压缩方法列表,一个扩展列表。通过包含
SessionId
字段,它也赋予了客户端重新开始之前的会话的能力。
- ServerHello:ServerHello消息和ClientHello消息很类似,除了它只包含一个密码套件和一个加密方法。如果他包含了一个SessionId(例如:SessionId长度大于0),就是通知客户端在以后重用这个会话。
- Certificate:这个消息的消息体包含了一个公钥证书链。证书链使TLS支持证书分层和公钥基础设施(PKI:Public Key Infrastructures)
- ServerKeyExchange:该消息携带客户端需要从服务器获得的密钥交换算法参数,以便之后能使用对称加密。这是可选的,因为并非所有密钥交换都要求服务器明确地发送此消息。实际上,在大多数情况下,Certificate消息足以使客户端与服务器安全地通信一个预先密钥。这些参数的格式完全取决于所选择的密钥套件(CipherSuite),它先前由服务器通过ServerHello消息设置。
- CertificateRequest:当服务器需要客户端身份验证时使用它。在Web服务器中不常用,但在某些情况下非常重要。该消息不仅向客户端询问证书,还会告知哪些证书类型是可接受的。此外,它还指出那些证书机构是可信赖的。
- ServerHelloDone:该消息完成了握手协商的服务器部分。它没有附加信息。
- ClientKeyExchange:它为服务器提供必要的数据,以生成对称加密的密钥。消息格式与ServerKeyExchange非常相似,因为它主要取决于服务器选择的密钥交换算法。
- CertificateVerify:客户端使用该消息来证明服务器拥有与其公钥证书相对应的私钥。该消息保存有客户端数字签名的散列信息。如果服务器向客户端发出CertificateRequest,则需要发送需要验证的证书。同样的,信息的确切尺寸和结构取决于商定的算法。在所有情况下,输入到哈希函数的信息是相同的。
- Finished:此消息表示TLS协商已完成,密码套件(CipherSuite)已激活。应该发送已经加密,因为协商成功完成,所以必须在此之前发送一个
ChangeCipherSpec
协议消息来激活加密。Finish消息包含一个用所有先前的握手消息组合的哈希,其后是识别服务器/客户端角色的特殊号码,主密钥和填充。所产生的哈希与CertificateVerify
哈希不同,因为存在更多的握手消息。
ChangeCipherSpec协议格式
这是最简单的协议:它只有一条消息。该消息必须是单独的协议而不是握手协议的一部分的原因是由于记录层封装(Record Layer encapsulation)。 TLS协议同时对整个记录层消息进行加密。 ChangeCipherSpec消息表示加密的激活,并且由于加密不能应用于消息的一部分,因此任何其他消息都不可能遵循ChangeCipherSpec。避免这些组合的最佳方法是将此消息升级为协议状态。
下面展示ChangeCipherSpec消息是如何构造的:
Alert协议格式
警报协议也很简单。它定义了两个字段:严重性级别和警报描述。第一个字段表示警报的严重性(警告为1,致命的2),而第二个字段则表示准确的条件。支持的警报描述取决于SSL/TLS版本。
ApplicationData协议格式
该协议的任务是正确地封装来自网络堆栈的应用层的数据,从而可以通过底层协议(TCP)无缝地处理数据,而不会强制更改任何这些层。此协议中消息的格式遵循与以前协议相同的结构。
分析SSL/TLS流量
我将使用Wireshark进行抓包。客户端基于Scheme的方式支持SSL/TLS,服务端是OpenSSL分发的一部分(具有证书)。服务器监听在443端口,所有通信将通过环回设备进行。在Wireshark中将视图限制为TLS数据包的最简单方法是使用协议过滤器“ssl”。
第一个包(客户端->服务端)
一旦服务器运行并等待连接,客户端就可以启动它。这是客户端发送的第一个数据包
第二个包(服务端->客户端)
第一个包只包含一个从客户端发送到服务器的TLS握手消息(ClientHello)。然而,服务器发送给客户端的作为ClientHello响应的下一个TCP数据包携带3个握手消息。这些消息是ServerHello,Certificate和ServerHelloDone(没有发送ServerKeyExchange或CertificateRequest)。在下一个数据包之后,我将省略较低的堆栈协议(TCP/IP)。
第三个包(客户端->服务端)
目前看上去都是正确的,与上述的协议一致。客户端和服务器现在已经同意使用的算法(密钥交换的RSA,对称加密的AES-256-CBC和消息散列的SHA),压缩(无压缩)和使用的TLS扩展(SessionTicket TLS,重新谈判信息)。此外,客户端现在拥有服务器的证书,因此可以决定是否信任服务器。下一个数据包由客户端发送,并携带以下消息:ClientKeyExchange,ChangeCipherSpec,Finished(已经加密)。
第四个包(服务端->客户端)
在客户端发送ChangeCipherSpec和Finished之后,服务器预期执行相同的操作,以便双向启动采用对称密钥和所有协商的密码套件参数的加密通信。服务器必须发送自己的ChangeCipherSpec和Finished的消息,以使握手过程可以被认为是成功的。在这个消息中发生的一件非常有趣的事情是,我们看到其中一个扩展,扩展名称为Transport Layer Security(传输层安全TLS)会话恢复,而无需服务器端状态,它明确说明了它的作用。你应该记得,它被要求作为我们的ClientHello的一部分,并由服务器在其ServerHello中实现。有关与此扩展相关的消息的信息,我们需要查找RFC5077规范。如文档中所述,此扩展握手消息已被分配号码4。
应用层数据(客户端<->服务端)
此时,客户端和服务器完成握手。加密通信就位,应用数据可以安全传输。这是一个示例记录,单个TCP数据包可以携带几个消息:
关闭连接
由于我们的连接是加密的,所以想知道在数据包中发送什么的唯一方法是使Wireshark或类似的工具知道传输中使用的密钥。尽管这是可能的,我认为为了分析的目的,当客户端或服务器主动关闭连接时,知道客户端会发出一个警告消息就足够了。此警报消息的类型应为CloseNotify(类型0),但是我们将无法从原始数据中看到它。在这种情况下,客户端是以下警报消息的发件人:
结语
从流量分析可以看出,标准库和客户端的实现遵循了TLS 1.0规范。希望这对您了解SSL/TLS协议的内部机制是有用的。我发现检查十六进制原始数据(特别是像Wireshark这样强大的工具)是一种非常有意义和有趣的工作方式。