翻译自RFC6101
第五节 SSL协议
SSL是一个分层的协议,每层封包可能包括了长度、描述以及内容字段。SSL接受要传输的信息,并将这些信息(或者其压缩的结果,这是可选的)分片为可管理的块,然后添加MAC(message authenticate code,信息认证码)并加密,最后把封装好的信息传输出去。接收数据是通过解密、身份认证、解压缩、重组最后把结果丢给更高层客户端进一步处理。
5.1 会话和连接的状态
SSL是一个状态机,其客户端和服务器端的状态是通过SSL握手协议来协调的,从而使得它们俩的状态机可以协同工作,尽管事实上状态并非是相同的。(译者注:握手协议是SSL的管理协议,状态变化时,可以发出管理信息,从而协调C/S的状态,尽管他们俩的状态并非是同一个状态)逻辑上说一共有两种状态,一种是当前运行的状态,另一种是在握手协议期间pending的状态,此外,还分离了读和写的状态。
当客户端或者服务器端收到一个change cipher spec(密码规范改变)消息时,它拷贝pending读状态到当前读状态;当握手过程结束的时候,客户端和服务器端会交换它们的change cipher spec消息(5.3节),然后通过新的协商好的cipher spec来通讯。
一个SSL会话可能包括多个安全连接,通讯的每一方都可以有多个并发的会话。
会话的状态包括下列元素:
会话标识符:服务器指定的一个任意字节序列,用于识别一个活动的或者重用的会话状态。
peer认证: X509.v3认证一个peer,该栏位可以为NULL。
压缩方法: 在加密前用于压缩数据的算法。
密码规范: 指定批量数据压缩算法(例如NULL、DES等)以及MAC算法(例如MD5和SHA)。它也指明了压缩算法的属性,例如hash_size等。
主密钥: 客户端和服务器端共享的48bit的密钥。
可重用: 标志位,用来表征这个会话是否可以用来初始化新的连接。
连接的状态包括下列元素:
服务器端和客户端随机数:客户端和服务器端为每个连接选择的一个随机字节序列。
服务器端MAC写密钥:MAC操作由服务器端写入的数据的密钥
客户端MAC写密钥: MAC操作由客户端写入的数据的密钥
服务器写密钥: 用于服务器端数据的加密和客户端数据解密的密钥
客户端写密钥: 用于客户端数据的加密和服务器端数据的解密的密钥
初始化向量:
队列数目: 每一方都保留了各自的每个连接中用于传输和接受消息的队列数目,当一方发送或者收到一个change cipher spec消息,对应的队列数目被置为0。队列数目是uint64型,因此不可以超过2^64-1个。
5.2 记录层(Record Layer)
SSL的记录层从更高层接收未处理的原始数据,并将其保存到任意大小的非空的块中。
5.2.1 分片
记录层将信息块中的数据分片为SSLPlaintext记录,它最大不超过2^14字节。记录层并不保留客户端数据的边界(例如,相同ContentType的客户端数据可能被合并到同一个SSLPlaintext记录中)。
struct {
uint8 major, minor;
} ProtocolVersion;
enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
opaque fragment[SSLPlaintext.length];
} SSLPlaintext;
type: 处理分片信息的高层协议。
version: 使用的协议版本,当前是v3.0
length: 后面的fragment的长度(字节),应该不超过2^14。
fragment: 应用层的数据,这个数据对SSL是透明的,并由type指定的高层协议当作一个独立的块来处理。
注意:不同SSL记录层ContentType的数据可能被交叉存储,应用数据通常比其他content type的传输优先级要低。
5.2.2 记录压缩和解压
所有的记录都会被压缩,压缩算法由当前会话状态的压缩算法栏位来指定。通常总有一个活动的压缩算法,但是,这个栏位初始化的时候会被置为NULL。
压缩算法将一个SSLPlaintext结构体翻译为一个SSLCompressed结构体,压缩算法会在CipherSpec被重置的时候清除它们的比特位。
注意:CipherSpec是5.1节描述的会话状态的一部分,其详细的说明见附录A.7。
压缩必须是无损的,并且不可以增加内容的长度超过1024字节,如果解压函数遇到了一个SSLCompressed.fragment可能被解压出超过2^14字节的内容,它将发起一个fatal的解压错误。(5.4.2节)
struct {
ContentType type; /* same as SSLPlaintext.type */
ProtocolVersion version;/* same as SSLPlaintext.version */
uint16 length;
opaque fragment[SSLCompressed.length];
} SSLCompressed;
length: fragment的长度(字节),应该不超过2^14+1024.
fragment: SSLPlaintext.fragment的压缩形式。
注意:一个为NULL值的压缩算法是合法的,没有任何警告会发生(附录A.4.1)
实现注意:解压函数应当检查内容不会导致buffer的溢出。
5.2.3 记录载荷保护和加密规范(CipherSpec)
所有的记录都通过当前加密规范中指定的压缩和MAC算法来保护,总会有一个活动的加密规范,然而,初始化时它将被置为SSL_NULL_WITH_NULL_NULL,它不提供任何安全性。
一旦握手过程结束,双方已经共享了用于记录压缩和计算加密MAC的密钥。用于进行压缩和计算加密MAC的算法由CipherSpec指定,包含在CipherSpec.cipher_type中。压缩和MAC函数将SSLCompressed结果体翻译为SSLCiphertext结构体,解压函数则是相反的过程。传输也包含了一系列的字段来表示丢失、警告或者额外的信息。
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
select (CipherSpec.cipher_type) {
case stream: GenericStreamCipher;
case block: GenericBlockCipher;
} fragment;
} SSLCiphertext;
type: 和SSLCompressed.type相同
version: 和SSLCompressed.version相同
length: 后面的SSLCiphertext.fragment的长度(字节),不超过2^14+2048(SSLCompressed.length + hash_size)
fragment: SSLCompressed.fragment的加密形式,包括MAC
5.2.3.1 NULL或者标准流加密
流加密将SSLCompressed.fragment结果体转化为SSLCiphertext.fragment结构体,流解密则是相反的过程。(见附录A.7)
stream-ciphered struct {
opaque content[SSLCompressed.length];
opaque MAC[CipherSpec.hash_size];
} GenericStreamCipher;
MAC是这样生成的:
hash(MAC_write_secret + pad_2 +hash (MAC_write_secret + pad_1 + seq_num +SSLCompressed.type + SSLCompressed.length +SSLCompressed.fragment))
其中“+”表示连接。
pad_1: 对MD5,重复0x36字符48次;对SHA,重复0x36字符40次。
pad_2: 对MD5,重复0x5c字符48次;对SHA,重复0x5c字符40次。
seq_num: 这条信息的队列数目。hash: 这个加密方式的hash算法。
注意:MAC在加密之前就计算好了,流压缩压缩整个块,包括MAX。对于不使用同步化向量的流压缩(例如RC4),一个记录末尾的流压缩状态直接为后续的封包所用。如果压缩算法是SSL_NULL_WITH_NULL_NULL,那么压缩包括了认证操作(例如,数据没有被压缩且MAC为0表示MAC没有被用)。SSLCiphertext.length是SSLCompressed.length + CipherSpec.hash_size。
5.2.3.2 CBC(Cipher Block Chaining)块压缩
对于块压缩(例如RC2或者DES),压缩和MAC函数将SSLCompressed.fragment结构体翻译为SSLCiphertext.fragment结构体。
block-ciphered struct {
opaque content[SSLCompressed.length];
opaque MAC[CipherSpec.hash_size];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
} GenericBlockCipher;
MAC的生成和5.2.3.1一样。
padding: 该字段用于保证plaintext的长度是块压缩的块长度对齐而添加的内容。
padding_length: 该字段是padding的长度,必须比块压缩的块长度小,可以是0。
压缩的数据的长度SSLCiphertext.length应当比SSLCompressed.length、padding_length和CipherSpec.hash_size的和要大。
注意:在CBC时,第一个记录的初始化向量由握手协议来提供,后续的初始化向量是前一个记录的最后一个压缩块。
5.3 密码规范改变协议(Change Cipher Spec Protocol)
密码规范改变协议用于加密策略中的信号变化,该协议包括单个由当前CipherSpec指定的方法(而不是pending的那个)加密并压缩的报文。该报文包含了一个单个字节,其值为1。
struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
密码规范改变报文既可由服务器端也可以由客户端发送,用于通知接收方后续的记录将被刚才协商好的密码规范和密钥保护。接受到该报文,将引发接收方拷贝pending读状态到当前读状态。
客户端会在握手交换密钥和认证信息(如果有)之后发送密码规范改变报文,而服务器端会在成功处理客户端发送来的密钥交换报文之后,发送密码规范改变报文。一个意外的密码规范改变报文会产生一个unexpected_message警报(5.4.2节)。当重用上一个会话时,密码规范改变报文会在hello报文之后发出。
5.4 警报协议(Alert Protocol)
(略)
5.5 握手协议综述
SSL握手协议运行在记录层之上,会话状态加密参数是通过SSL的握手协议产生的。当一个SSL的客户端和服务器端开始通讯时,它们就协议版本、压缩算法、相互认证的方法(可选地)达成一致,然后使用公钥加密技术来生成公钥,上述过程都是在握手协议里面实现的,总结如下:
客户端发送client hello报文
服务器端必须响应server hello报文,否则会引发致命错误然后连接中断
client hello和server hello被用于建立增强的安全能力的客户端和服务器端之间的连接,它们建立了如下属性:协议版本、会话ID、密码规范以及压缩方法。另外还产生并交换了两个随机值:ClientHello.random和ServerHello.random
在hello报文之后,服务器端会发送它的证书,如果它将要被认证。另外,服务器key交换报文也可能被发送,如果要求的话(如果服务器没有证书,或者它的证书只用于签名)。如果服务器被认证,它还可能请求客户端的证书,如果这是所选的密码规范要求的。
然后,服务器端会发送server hello done报文,表示握手阶段的hello报文结束了,并等待客户端的响应。
如果服务器端发送了证书请求报文,那么服务器端要么发送证书报文,要么发送no_certificate警报。然后,发送客户端key交换报文,其内容基于server hello和client hello协商选出来的公钥算法生成。如果客户端发送的证书带有签名功能,那么一个数字签名证书认证报文会被发送,用于严格地认证这个报文。
这时候,客户端会发送密码规范改变报文(见5.3节),并拷贝pending CipherSpec到当前CipherSpec。然后,客户端立即基于新的算法、key和密钥来发送完成报文,作为响应,服务器端会发送它自己的密码规范改变报文、拷贝pending CipherSpec到当前CipherSpec并且基于新的CipherSpec发送完成报文。
这时,握手协议完成,客户端和服务器端可以交换后续数据了。
注意:为了防止流水线中断,ChangeCipherSpec是一个独立的SSL协议的Content Type,而不是一个真正的SSL握手报文。
当客户端和服务器端决定重用一个之前的会话,或者复制已有的会话时(而不是初始化新的安全参数),其报文流程如下:
客户端发送client hello,使用被复用的会话的ID,服务器端会比对它的会话cache,如果找到匹配的ID,那么服务器端会愿意在特定的会话状态下重新建立这个连接,从而发送一个server hello,其会话ID和客户端的相同。
这时,客户端和服务器端都必须发送密码规范改变报文,并直接进入完成报文阶段。
一旦重新连接建立,客户端和服务器端可以交互数据了。
如果匹配的ID没有找到,服务器端会生成一个新的会话ID,SSL客户端和服务器端就要进行完整的握手了。