ITKeyword,专注技术干货聚合推荐

注册 | 登录

传输层安全协议(TLS)1.2版

u011130578 分享于 2016-02-03

推荐:TLS:安全传输层协议

TLS:安全传输层协议 TLS:Transport Layer Security000 概况 安全传输层协议(TLS)用于在两个通信 应用程序之间提供保密性和 数据完整性。该协议由两层组成:

2019阿里云全部产品优惠券(新购或升级都可以使用,强烈推荐)
领取地址https://promotion.aliyun.com/ntms/yunparter/invite.html

1.介绍 TLS协议的主要目标是在两个通信应用之间提供私密性和数据完整性。这个协议由两层组成:TLS记录协议和TLS握手协议。最低层是基于一些可靠传输协议(如TCP)的TLS记录协议。TLS记录协议提供的连接安全有两个基本性质: 连接是私有的。对称密码学被用于数据加密(如:AES,RC4等)。对称加密的密钥对每条连接都是独特的,而且是基于另外一种协议(如TLS握手协议)进行的秘密协商而生成的。记录协议也能用于无加密的情况。 连接是可靠的。消息的传输包含了一个使用基于密钥的MAC的消息完整性检查。安全hash函数(如SAH-1等)被用于MAC计算。记录协议可以不使用MAC,但这种模式只能用于TLS握手协议使用记录协议传输消息来协商安全参数的情况。

TLS记录协议被用于封装各种高层协议。其中一种被封装的协议就是TLS握手协议,这种协议允许服务器和客户端在应用层协议传输或接收第一个字节数据之前验证彼此的身份并协商一个加密算法和加密密钥。TLS握手协议提供的连接安全有三个基本性质: 对端的身份能够使用非对称(或公钥)密码学(如RSA,DSA等)来验证。这个验证是可选的,但通常要求至少验证一方。 共享机密的协商是安全的:窃听者无法得到所协商的机密,对于已认证的连接,攻击者即使把自己置于连接的中间也是无法得到机密的。 协商是可靠的:攻击者不能在不被通信的参与者获知的情况下修改协商数据。

TLS的一个优点是它是应用层协议无关的。高层协议可以透明地置于TLS协议之上。然而,TLS标准不会指定协议怎样为TLS增加安全性。关于怎样初始化TLS握手、怎样理解认证证书交换的相关决策,则留给运行于TLS之上的协议的设计者和实现者做判断。 1.1需求术语 略。 1.2与TLS1.1的主要差别

本文是TLS 1.1协议的一个修订,修订内容包括提升了灵活性,尤其是密码学算法的协商。主要变更如下: 在伪随机函数中合并使用MD5和SHA-1算法的做法被在伪随机函数中指定密码协议族的方法所取代。本文中所有的密码协议族使用P_SHA256。 在数字签名要素中合并使用MD5和SHA-1算法的做法被单独的hash算法取代。现在的签名要素包含一个显式指定所用hash算法的域。 对客户端和服务器的能力进行实质性的清理,以便指定他们能接受的hash和签名算法。这样也会减小使用以前版本TLS中的签名和hash算法的约束。 增加了对“以其它数据模式进行认证加密”的支持。 增加了对TLS扩展的定义和AES协议族的支持。 对加密的预主密钥的版本号进行更严格的检查。 严格限定了一些需求。 Verifi_data的长度现在取决于密码协议(默认仍然是12)。 清除了对Bleichenbacher/Klima攻击防御的描述。 在很多情况下必须发送警报消息。 在发出一个“证书请求”消息之后,如果客户端没有收到证书,则必须发送一个空证书列表。 TLS_RSA_WITH_AES_128_CBC_SHA现在是一个强制实现的密码协议族。 增加了HMAC-SHA256密码协议族。 移除了IDEA和DES协议族,它们已经不被赞成使用,且会在一个单独的文档中说明。 对SSLv2后向兼容hello的支持现在是“可以”而非“应该”(支持的方式为回应”SHOULD NOT“);将来这种支持很可能成为“”不应该“。 在演示语言中增加受限的”通过“,以便允许多种情况的arms拥有相同的编码。(原文:Added limited "fall-through" to the presentation language to allow multiple case arms to have the same encoding.) 增加了章节描述关于实现上的易犯的错误 通常的澄清和编辑工作 2.目标 TLS协议的目标按照优先级排列如下: 密码安全:TLS应当被用于在通信双方之间建立一个安全连接。

互操作性:不相关的程序员们应当能够在不了解他人代码的情况下使用TLS开发出能成功地交换密码参数的应用。 可扩展性:TLS致力于提供一个框架,新的公钥和块加密方法能够作为必要内容被合并进去。这样也会实现两个子目标:阻止了创造新协议的需要(和引入可能存在新弱点的风险)和避免了实现一整套新安全库的需要。 相对效率:密码操作趋于高CPU消耗,尤其是公钥操作。正因如此,TLS协议已经结合了一个可选的会话缓存机制以减小连接(that need to be

established from scratch)的数量。此外,关于减少网络活动(译者注:应该指的是流量)也获得了关注。 3.本文的目标

本文和TLS协议自身都是基于由Netscape公布的SSL 3.0规范。TLS和SSL3.0的差异并不是引人注目的,但差异也足够大到使各种版本的TLS和SSL3.0不能互操作(虽然每种协议都包含一个机制使其能够兼容之前的版本)。本文的主要目标读者是协议实现者和对协议进行密码分析的人员。这个思想贯穿于这篇规范的制定过程,并致力于反应这两部分群体(译者注:协议实现和协议分析人员)的需求。出于这个原因,很多算法相关的数据结构和规则被包含在文档的内容中(或附录中),以方便获取。

本文并不致力于提供任何关于服务定义和接口定义的细节,虽然处于维护安全的坚实性,它确实有选择地覆盖了一些策略的区域。 4.陈述语言

本文使用另外的表示方法处理数据格式。下面会用到非常基础甚至是有些随便定义的陈述语法。这种语法源自多处。虽然它在语法上像编程语言C,在语法和目的上像XDR(External Data Representation),但有太多相似之处是有风险的。这种陈述语言只用于将TLS文档化,在这个特定目标外该语言不会有普遍的应用。 4.1 基本块大小

所有数据条目的描述都是被显示指定的。基本数据块大小是1个字节(即8位)。多个字节数据条目是字节的串联,从左到右,从上到下。从字节流的角度 看,一个多字节条目(在例子中是一个数值)的组织方式(使用C的记法)如下: value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) | ... | byte[n-1]; 对于多字节数值这个字节序是普通的网络字节序或大端格式。 4.2 其它

注释以"/*"开头,以"*/"结束。

可选组件通过将其包含在"[[ ]]" 双括号中来表示。

包含未解释数据的单字节实体属于opaque类型。 4.3 向量

一个向量(一维数组)是一个同类数据元素的流。向量的大小可能在编写文档时指定或留待运行时确定。在任何情况下,向量的长度都是指字节数而非元素数。定义一个新类型T'(是一个固定长度的类型T的向量)的语法是:

T T'[n];

这里,T'在数据流中占据了n个字节,而n是多个类型T的大小。向量的长度并不包含在编码流中。

在下面的例子中,Datum被定义为协议不能理解的3个连续字节, 而Data是三个连续的Datum,共占据9个字节。

opaque Datum[3];

/* 三个未知字节 */

Datum Data[9];

/* 3个连续的3字节向量 */

变长向量的定义是通过指定一个合法长度的子范围来实现(使用符号<floor..ceiling>)。当这些被编码时,在字节流中实际长度是在向量的内容之前。这个长度会以一个数字的形式出现,并消耗足够多的字节以便表示向量的最大长度(ceiling)。一个实际长度是0的变长向量会被当做一个空向量。

T T'<floor..ceiling>;

在下面的例子中会强制要求一个向量必须包含300-400个字节的opaque类型数据,它不能为空。实际长度域占用两个字节,一个uint16,这足以代表数值400(见4.4节)。另外一方面,更长的向量可以描述多达800字节的数据,或400个uint16类型的元素,这个向量可以为空。它的编码会包含一个两字节的实际长度域设置在向量前。一个编码向量的长度必须是单个元素长度的偶数倍(例如,一个17字节长的uint16类型的向量是非法的)。

opaque mandatory<300..400>;

/*长度域是2字节,不能为空 */

uint16 longer<0..800>;

/* 0-400 16-bit 无符号整数 */ 4.4 数字

基本数字数据类型是一个无符号字节(uint8)。所有更大的数字数据类型都被组织成固定长度的字节序列并如4.1节中所描述的那样被串联,且同样是无符号的。下面是预定义的数字类型。

uint8 uint16[2];

uint8 uint24[3];

uint8 uint32[4];

uint8 uint64[8];

本篇规范中的所有的数值都是以网络字节序(大端)存储;一个uint32类型的十六进制字节01 02 03 04等于十进制数16909060。

需要注意的是在一些情况下(如DH参数)有必要将整数以不透明(译注:opaque)向量的形式表示。在这种情况下,它们代表无符号整数(即,最开始的0字节是不需要的,即使设置了最有意义的bit(译注:不明白这个bit是什么))。 4.5 枚举

另外一种少见的数据类型是枚举。一个枚举类型的域仅能表示定义时声明的值。每个定义都是一个不同的类型。只有相同类型的枚举能被指定或比较。一个枚举的每个元素必须被指定一个值,就像下面的例子所表明的。既然枚举类型的元素并不是有序的,它们能够被以任意顺序指定任意独一的值。

enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

一个枚举在字节流中占据的空间足够存储其定义的最大有序数值。下面的定义会使用1个字节来表示Color类型的域。

enum { red(3), blue(5), white(7) } Color;

一个选择是指定一个值但不关联标记以强制定义枚举的大小,这样无需定义一个多余的元素.在下面这个例子中,Taste在字节流中会消耗2个字节, 但只能表示数值1,2,或4。

enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

一个枚举类型的元素的名称被局限于定义的类型。在第一个例子中,对枚举的第二个元素的完全合格的引用是Color.blue,如果赋值的目标是被 很好地指定(译注:这句话的意思应该不会引起冲突,或叫二义性),则这样的格式是不需要的。

Color color = Color.blue;

/* 过度指定, 合法 */

Color color = blue;

/* 正确, 类型隐藏 */

对于不能转化为外部表示的枚举(原文:external representation),其数字信息会被忽略。

enum { low, medium, high } Amount; 4.6构造类型

出于方便,结构体类型可以由原始类型构建。每个规范声明了一个新的、独特的类型。定义的语法很像C语言:

struct {

T1 f1;

T2 f2;

...

Tn fn;

} [[T]];

结构体内的域可以用类型的名字来描述,使用类似于枚举的语法。例如,T.f2引用了前面定义的结构的第二个域。结构体的定义可以嵌套。 4.6.1 变量

基于从环境中获得的知识,定义的结构体可以有一些变量。选择符必须是一个定义了结构体中变量取值范围的枚举类型。在select中声明的每个枚举元素必须有一个case条件。Case条件有受限的通过条件:如果两个case条件中间紧紧相连,它们中间没有域,则它们拥有相同的域。因此,在下面的例子中,"orange"和"banana"都包含V2,注意这是TLS 1.2中的一个新的语法。

变量结构体的体可以添加一个标签用于引用。通过这个机制变量可以在运行时被选择,并不会被描述语言所限制。

struct {

T1 f1;

T2 f2;

....

Tn fn;

select (E) {

case e1: Te1;

case e2: Te2;

case e3: case e4: Te3;

....

case en: Ten;

} [[fv]];

} [[Tv]];

例如:

enum { apple, orange, banana } VariantTag;

struct {

uint16 number;

opaque string<0..10>; /*变长*/

} V1;

struct {

uint32 number;

opaque string[10];

/* 固定长度 */

} V2;

struct {

select (VariantTag) { /*selector的值是隐晦的 */

case apple:

V1;

/* 变量体, tag = apple */

case orange:

case banana:

V2;

/*变量体, tag = orange or banana */

} variant_body;

/* 变量的可选标签*/

} VariantRecord; 4.7 密码属性

5个密码操作(数字签名,流密码加密,块密码加密,AEAD(authenticated encryption with)加密,公钥加密)分别被命名为:数字签名、流加密、块加密、aead加密和公钥加密。一个域的密码处理流程是通过将一个经过适当合适的关键字放在域类型规范之前来指定的。密码密钥由当前连接的状态所暗示的(见6.1节) 一个数字签名元素被编码为一个DigitallySigned结构:

struct {

SignatureAndHashAlgorithm algorithm;

opaque signature<0..2^16-1>;

} DigitallySigned;

algorithm域指定了所使用的算法(见7.4.1.4.1节对这个域的定义)。需要注意的是algorithm域的介绍是改变自以前的版本。signature是一个使用这些算法对内容元素进行的数字签名。这些内容本身不会出现在网络通路上但可以被简单计算出来。signature的长度由签名算法和密钥确定。

对于RSA签名,opaque向量包含使用RSASSA-PKCS1-v1_5签名方案(定义在[PKCS1]中)所生成的signature。 正如在[PKCS1]中所讨论的,DigestInfo必须是DER编码的[X680][X690]。对于没有参数的hash算法(包括SHA- 1),DigestInfo.AlgorithmIdentifier.parameters 域必须是NULL,但实现上必须接受有无参数和NULL参数。需要注意的是TLS早期版本使用了一个不同的RSA签名方案,这个方案不包含 DigestInfo编码。对于DSA,20字节的SHA-1hash会通过数字签名算法来直接运行,不需要添加额外的hash算法。这样产生了两个值,r和s。DSA签名 是一个opaque向量,像上面的一样,其内容是DER编码的:

Dss-Sig-Value ::= SEQUENCE {

r INTEGER,

s INTEGER

}

注意:在当前的术语中,DSA是指数字签名算法,DSS是指NIST标准。在原始的SSL和TLS规范中,DSS使用得很广泛。本文使用 "DSA"是指算法,"DSS"是中标准,在代码中使用"DSS"是考虑到历史的连续性。

在流密码加密中,明文是同源自一个密码学安全密钥的伪随机数生成器的相同数量的输出进行的异或运算。

在块密码加密中,每个明文块被加密为一个密文块。所有的密文块都以CBC(密码分组链接模式)模式生成。所有的分块加密的元素都会是密文块长度的精确倍数。

在AEAD加密中,明文同时被加密和进行完整性保护。输入可以是任意长度,aead加密输出通常比输入大,以容纳完整性检验值。

在公钥加密中,一个公钥算法所加密的数据只有使用匹配的私钥才能解密。一个公钥加密元素被编码成一个opaque向量<0..2^16-1>,这里的长度由加密算法和密钥指定。

RSA加密可以使用RSAES-PKCS1-v1_5加密机制实现,这个机制在[PKCS1]中定义。

在下面的例子中,

stream-ciphered struct {

uint8 field1;

uint8 field2;

digitally-signed opaque {

uint8 field3<0..255>;

uint8 field4;

};

} UserType;

内部结构(field3和field4)的内容被用作签名/hash算法的输入,然后整个结构体被流密码加密。这个结构体的长度按字节计算,等于field1和field2所用的2个字节,加上签名和hash算法用的2个字节,加上签名长度用的两个字节,加上签名算法输出的长度。签名的长度是已知的,因为签名所用的算法和密钥在编码或解码这个结构体之前就已经知道了。 4.8 常量

出于规范性,固定类型的常量的定义可以通过声明一个所需类型的符号并赋值来实现。

不确定的类型(译注:原文是“Under-specified types”)(opaque,变长向量,和包含opaque类型的结构体)不能被赋值。多元素结构体或向量的赋值可以忽略域的名称。

例如:

struct {

uint8 f1;

uint8 f2;

} Example1;

Example1 ex1 = {1, 4};

/* 设置 f1 = 1, f2 = 4 */ 5. HMAC和伪随机函数

TLS记录层使用一个有密钥的信息验证码(MAC)来保护信息的完整性。本文中定义的密码算法族使用了一个被称为HMAC(在[HMAC]中描 述)的MAC算法,它基于一个hash函数。如果必要的话其它密码算法族可以定义它们自己的MAC算法。

此外,为了进行密钥生成或验证,需要一个MAC算法对数据块进行扩展以增加机密性。这个伪随机函数(PRF)将机密信息(secret),种子和 身份标签作为输入,并产生任意长度的输出。

在本节中,我们基于HMAC定义了一个PRF。这个使用SHA-256 hash函数的PRF被用于所有的密码算法族,这些密码算法在本文中定义,或在本文之前、在TLS1.2的协商阶段就发表的TLS文献中定义。新的密码算 法族必须显式指定一个PRF,通常应该将SHA-256或更强的标准hash算法与TLS PRF一同使用。 首先,我们定义一个数据扩展函数,P_hash(secret, data),它使用一个hash函数扩展一个secret和种子,形成任意大小的输出:

P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +

HMAC_hash(secret, A(2) + seed) +

HMAC_hash(secret, A(3) + seed) + ...

这里"+"是指级联。

A()被定义为:

A(0) = seed

A(i) = HMAC_hash(secret, A(i-1))

必要时P_hash可以被多次迭代,以产生所需数量的数据。例如,如果P_SHA256被用于产生80字节的数据,它应该被迭代3次(通过 A(3)),产生96字节的输出数据;最终迭代产生的最后16字节会被丢弃,留下80字节作为输出数据。

TLS的PRF可以通过将P_hash运用与secret来实现:

PRF(secret, label, seed) = P_<hash>(secret, label + seed)

label是一个ASCII字符串。它应该以严格地按照它被给出的内容进行处理,不包含一个长度字节或结尾添加的空字符。例 如,label"slithy toves"应该通过hash下列字节的方式被处理:

73 6C 69 74 68 79 20 74 6F 76 65 73

(译注:上述数据是字符串"slithy toves"的十六进制格式) 6.TLS记录协议

TLS记录协议是一个层次化的协议。在每一层中,消息都可能包含长度、描述、内容等域。记录协议承载被发送的消息,将数据分片为可管理的块,有选择地压缩数据,应用MAC,加密,传输最终数据。接收到的数据被解密,验证,解压缩,重组,然后传递给高层客户。

本文中描述了4个使用记录协议的协议:握手协议,告警协议,更改密码规格协议,和应用数据协议。为了支持TLS协议扩展,额外的记录内容类型也可以被记录协议支持。新的记录内容类型值被IANA在TLS内容类型注册(在12节描述)中分配。

应用不能发送本文没有定义的记录类型,除非经过一些扩展的协商。如果一个TLS实现接收到一个非期望的记录类型,它应该发送一个“unexpected_message”告警消息。

任何被设计用来使用TLS的协议必须严格地设计以处理可能的攻击。从实践上来说,者意味着协议设计者必须意识到TLS能够实现何种安全特性,无法提供或不能安全地依赖后者。

特别需要注意的是一个记录消息的类型和长度不能使用加密保护。如果这个信息本身是敏感的,应用设计者可能会希望采取一些措施(填充,覆盖流量(译注:原文是“cover traffic”))以减小信息泄露。 6.1 连接状态

一个TLS连接的状态就是TLS记录协议的操作环境。 它指定了一个压缩算法,一个加密算法,一个MAC算法。此外,这些算法的参数必须是已知的:用于连接的读、写两个方向的MAC密钥和块加密密钥。 逻辑上总是有4个状态比较突出:当前读和写状态,挂起的读和写状态。所有的记录协议都在当前的读写状态下处理。挂起状态的安全参数可以通过TLS 握手协议来设置,而ChangeCipherSpec可以有选择地设置当前状态为挂起状态,在这种情况下适当的当前状态被设置,并被挂起状态所替 代; 挂起状态随后会被重新初始化为一个空状态。将一个状态未经安全参数的初始化就设置为一个当前状态是非法的。初始当前状态一直会指定不使用加密,压缩或 MAC。 一个TLS连接读写状态的安全参数可以通过提供如下值来设定:

连接终端

在这个连接中这个实体被认为是“client”或“server”。

PRF算法

被用于从主密钥生成密钥的算法(见5和6.3节)。

块加密算法

被用于块加密的算法。本规范包含了这种算法的密钥长度,它是成块加密,流加密,或AEAD加密,密文的块大小(如果合适的话),和显示和隐式初始化向量 (或nonces)的长度

MAC算法

被用于消息验证的算法。本规范包含了MAC算法返回值的长度。

压缩算法

用于数据压缩的算法。被规范必须包含算法执行压缩所需的所有信息。

主密钥

在连接的两端之间共享的48字节密钥

客户端随机数

由客户端提供的32字节随机数

服务器随机数

由服务器提供的32字节随机数

上述参数通过描述语言定义如下:

enum { server, client } ConnectionEnd;

enum { tls_prf_sha256 } PRFAlgorithm;

enum { null, rc4, 3des, aes }

BulkCipherAlgorithm;

enum { stream, block, aead } CipherType;

enum { null, hmac_md5, hmac_sha1, hmac_sha256,

hmac_sha384, hmac_sha512} MACAlgorithm;

enum { null(0), (255) } CompressionMethod;

/* CompressionMethod, PRFAlgorithm,

BulkCipherAlgorithm, 和 MACAlgorithm 指定的算法可以增加 */

struct {

ConnectionEnd

entity;

PRFAlgorithm

prf_algorithm;

BulkCipherAlgorithm

bulk_cipher_algorithm;

CipherType

cipher_type;

uint8

enc_key_length;

uint8

block_length;

uint8

fixed_iv_length;

uint8

record_iv_length;

MACAlgorithm

mac_algorithm;

uint8

mac_length;

uint8

mac_key_length;

CompressionMethod

compression_algorithm;

opaque

master_secret[48];

opaque

client_random[32];

opaque

server_random[32];

} SecurityParameters;

记录层会使用安全参数产生如下的6个条目(其中的一些并不是所有算法都需要的,因此会留空):

client write MAC key

server write MAC key

client write encryption key

server write encryption key

client write IV

server write IV

当server端接收并处理记录时会使用client写参数,反之亦然。使用安全参数来生成这些条目的算法在6.3节中描述。

一旦安全参数被设定且密钥被生成,连接状态就可以将它们设置为当前状态来进行初始化。这些当前状态必须在处理每一条记录后更新。每个连接状态包含如下元素:

压缩状态

压缩算法的当前状态

密码状态

加密算法的当前状态。这个状态由连接的预定密钥组成。对于流密码,这个状态也将包含对流数据进行加解密所需的任何必要的状态信息。

MAC密钥

当前连接的MAC密钥,以前述方式生成。

序列号

每个连接状态包含一个序列号,读状态和写状态分别维持一个序列号。当一个连接的状态被激活时序列号必须设置为0.序列号的类型是uint64,所以序列号大小不会 超过2^64-1。序列号不能回绕。如果一个TLS实现需要回绕序列号,则必须重新协商。一个序列号在每条记录信息被发送之后增加:特别地,在一个特殊连接状态下发送的 第一条记录消息必须使用序列号0. 6.2 记录层

TLS记录层以任意大小的非空块从高层接收无解释的数据。 6.2.1. 分片

记录层将信息块分片为携带2^14字节或更小的大块数据的TLSPlaintext。client信息边境并不在记录层保留(即,多个同一内容类型的client信息会被合并成一个TLSPlaintext,或者一个消息会被分片为多个记录)。

struct {

uint8 major;

uint8 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[TLSPlaintext.length];

} TLSPlaintext;

type

用于处理封装的分片的高层协议

version

协议的版本。本文所描述的TLS 1.2的版本是{3, 3}。版本值3.3是基于历史的,因为TLS 1.0使用的是{3, 1}(见附录A.1.)。需要注意的是一个支持多个版本TLS的client在收到ServerHello之前可能并不知道版本是什么。关于ClientHello中使用什么样的记录层版本号的讨论见附录E。

length

接下来的TLSPlaintext.fragment的长度(以字节计) 。这个长度不能超过2^14.

fragment

应用数据。这种数据是透明的并且作为一个独立的块由type域所指定的高层协议来处理。

实现上不能发送fragments长度为0的握手,报警,或ChangeCipherSpec内容类型。发送fragment长度为0的应用数据在进行流量分析时是有用的。

注意:不同TLS记录层内容类型的数据可能是交错的。应用数据的传输优先级通常低于其它内容类型。然而, 记录必须以记录层能提供保护的顺序传递到网络中。接收者必须接收并处理一条连接中在第一个握手报文之后交错的应用层流量. 6.2.2. 记录压缩和解压缩

所有的记录都使用在当前会话状态中定义的压缩算法进行压缩。这里的压缩算法必须一直是激活的;然而,初始时它被定义为CompressionMethod.null。压缩算法将一个TLSPlaintext结构转换为一个TLSCompressed结构,在连接状态被激活时压缩函数会由默认状态信息进行初始化。[RFC3749]描述了用于TLS的压缩算法.

压缩必须是无损的,也不能增加内容的长度超过1024字节。如果解压函数遇到一个TLSCompressed.fragment,其解压后的函数超过2^14字节,则必须报告一个fatal压缩失败错误。

struct {

ContentType type;

/* 与TLSPlaintext.type相同 */

ProtocolVersion version;/* 与TLSPlaintext.version相同 */

uint16 length;

opaque fragment[TLSCompressed.length];

} TLSCompressed;

length

接下来的TLSCompressed.fragment的长度(以字节计) 。这个长度不能超过2^14 + 1024.

fragment

TLSPlaintext.fragment压缩后的形态

注意:一个CompressionMethod.null的操作是恒等操作,不改变任何域。

实现注意:解压函数需要保证消息不会导致内部缓存溢出。 6.2.3 记录载荷保护

解密和MAC函数将一个TLSCompressed结构转换为TLSCiphertext,加密函数实现相反的过程。记录的MAC包含了一个序列号用于感知丢失、增加和重复的消息。

struct {

ContentType type;

ProtocolVersion version;

uint16 length;

select (SecurityParameters.cipher_type) {

case stream: GenericStreamCipher;

case block:

GenericBlockCipher;

case aead:

GenericAEADCipher;

} fragment;

} TLSCiphertext;

type

这个type域与TLSCompressed.type相同.

version

这个version域与TLSCompressed.version相同.

length

接下来的TLSCiphertext.fragment的长度(以字节计) 。这个长度不能超过2^14 + 2048.

fragment

TLSCompressed.fragment的加密形态, 加上MAC. 6.2.3.1. 空加密或标准流加密

流加密(包括BulkCipherAlgorithm.null;见附录A.6)将TLSCompressed.fragment结构转换为流的TLSCiphertext.fragment结构,或相反的操作。

stream-ciphered struct {

opaque content[TLSCompressed.length];

opaque MAC[SecurityParameters.mac_length];

} GenericStreamCipher;

MAC用如下方式产生:

MAC(MAC_write_key, seq_num +

TLSCompressed.type +

TLSCompressed.version +

TLSCompressed.length +

TLSCompressed.fragment);

这里的“+”表示连接。

seq_num

这个记录的序列号

MAC

由SecurityParameters.mac_algorithm指定的MAC算法

需要注意的是MAC在加密之前计算。流加密算法加密整个块,包括MAC。对于不使用同步向量的流加密算法(如RC4),流加密算法状态在一个记录的结束后就简单地用于随后的包。如果密码算法族是TLS_NULL_WITH_NULL_NULL,加密则由同一性操作构成(即数据不加密,MAC大小是0,意味着不使用MAC)。对于空算法和流加密,TLSCiphertext.length是TLSCompressed.length加上SecurityParameters.mac_length。 6.2.3.2. CBC块加密

对于块加密算法(如3DES或AES),加密和MAC函数将TLSCompressed.fragment转换为TLSCiphertext.fragment结构块或相反的操作。

struct {

opaque IV[SecurityParameters.record_iv_length];

block-ciphered struct {

opaque content[TLSCompressed.length];

opaque MAC[SecurityParameters.mac_length];

uint8 padding[GenericBlockCipher.padding_length];

uint8 padding_length;

};

} GenericBlockCipher;

MAC的产生方法与6.2.3.1节相同.

IV

初始化向量(IV)应该随机产生,并且必须是不能预测的。需要注意的是在TLS1.1以前的版本是没有IV域的。以前的记录中最后一个密文块(CBC的剩 余)被用作IV。使用随机IV是为了阻止在[CBCATT]中描述的攻击。对于块加密,IV的长度是 SecurityParameters.record_iv_length的值,这个值等于 SecurityParameters.block_size。

padding

Padding用于强制明文的长度是块加密块长度的整数倍,它可能是任意长度,最长是255字节,只要它使得TLSCiphertext.length是 块长度的整数倍。超出必要的长度在挫败基于对交换消息长度的分析进行的协议攻击时可能是必要的。在padding数据向量中的每个uint8必须 被填充padding的长度值。接收者必须检查这个pading且必须使用bad_record_mac alert来暗示padding错误。

padding_length

Padding的长度必须使GenericBlockCipher的总长度是密码块长度的整数倍。合法的取值范围是从0到255,包含0和 255.这个长度指定了padding域的长度但不包含padding_length域的长度。

密文数据的长度(TLSCiphertext.length)大于SecurityParameters.block_length, TLSCompressed.length, SecurityParameters.mac_length, 和padding_length之和。 例如:如果块长度是8字节,内容长度(TLSCompressed.length)是61字节,MAC长度是20字节,则填充签订长度是82字节 (不包含IV)。为了使总长度是8字节(块长度)的偶数倍,填充长度模8必须等于6.即填充长度可以是6, 14, 22, 以此类推, 直到254.如果有必要使填充长度最小,为6,则填充必须是6字节,每个字节都赋值为6.因此,GenericBlockCipher在块加密前的最后8 个字节可能是 xx 06 06 06 06 06 06 06, 这里xx是MAC的最后一个字节。

注意:对于CBC模式(密文分组链接模式)的块加密,关键的是记录的整个明文在传输任何密文之前就已被知道。否则,攻击者可能会发动 [CBCATT]中描述的攻击。 实现注记:Canvel et al. [CBCTIME]阐述了一个基于MAC计算时间的针对CBC填充的定时攻击。为了防御此类攻击,实现上必须确保物理填充是否正确记录的处理时间是基本一 样的。通常,做到这一点最好的方式是即使填充是错的也要计算MAC,然后只能丢弃整个包。例如,如果填充不正确,实现上可能假定一个0长度的填充 然后计算MAC。这样会留下一个小的定时通道,因为MAC计算性能某种程度上依赖于数据分片的长度,但不能确信这个长度会大到足够被利用,这是因 为现存MAC的块大而定时信号的长度小。 6.2.3.3. AEAD加密

对于AEAD[AEAD]加密(如:[CCM]或[GCM]),AEAD函数将TLSCompressed.fragment结构转换为AEAD TLSCiphertext.fragment结构或相反。

struct {

opaque nonce_explicit[SecurityParameters.record_iv_length];

aead-ciphered struct {

opaque content[TLSCompressed.length];

};

} GenericAEADCipher;

AEAD加密的输入有:单个密钥,一个nonce,一块明文,和被包含在验证检查中的“额外数据”(在[AEAD]2.1节中描述)。密钥是 client_write_key或者the server_write_key。不使用MAC密钥。

每个AEAD密码族必须指定提供给AEAD操作的nonce是如何构建的,GenericAEADCipher.nonce_explicit部 分的长度是什么。在很多情况下,使用在[AEAD]3.2.1节中描述的部分隐藏的nonce技术是合适的;record_iv_length就 是GenericAEADCipher.nonce_explicit的长度。在这种情况下,隐式部分应该作为client_write_iv和 server_write_iv从 key_block中(在6.3节中描述)推导出来,显示部分被包含在GenericAEAEDCipher.nonce_explicit中。

明文是TLSCompressed.fragment。

额外的验证数据(我们表示为additional_data)定义如下:

additional_data = seq_num + TLSCompressed.type +

TLSCompressed.version + TLSCompressed.length;

这里“+”表示连接。

AEAD的输出由AEAD加密操作所产生的密文输出构成。长度通常大于TLSCompressed.length,在量上会随着AEAD加密的不同而不同。因为加密可能包含填充,开销的大小可能会因TLSCompressed.length值二不同。每种AEAD加密不能产生大于1024字节的长度扩张。象征性地,

AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext,

additional_data)

为了解密和验证,加密算法将密钥、nonce、“额外数据”和AEADEncrypted的值作为输入。输出要么是明文要么是解密失败导致的错误。这里没有分离完整性检查。即:

TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce,

AEADEncrypted,

additional_data)

如果解密失败,会产生一个bad_record_mac致命警报。 6.3. 密钥计算

记录协议需要一个算法从握手协议提供的安全参数中生成当前连接状态(见附录A.6)所需的密钥。

主密钥被扩张为一个安全字节序列,它被分割为一个客户端写MAC密钥,一个服务端写MAC密钥,一个客户端写加密密钥,一个服务端写加密密钥。它们中的每一个都是从字节序列中以上述顺序生成。未使用的值是空。一些AEAD加密可能会额外需要一个客户端写IV和一个服务端写IV(见6.2.3.3节)。 生成密钥和MAC密钥时,主密钥被用作一个熵源。

为了生成密钥数据,计算

key_block = PRF(SecurityParameters.master_secret,

"key expansion",

SecurityParameters.server_random +

SecurityParameters.client_random);

直到产生足够的输出。然后,key_block会按照如下方式分开:

client_write_MAC_key[SecurityParameters.mac_key_length]

server_write_MAC_key[SecurityParameters.mac_key_length]

client_write_key[SecurityParameters.enc_key_length]

server_write_key[SecurityParameters.enc_key_length]

client_write_IV[SecurityParameters.fixed_iv_length]

server_write_IV[SecurityParameters.fixed_iv_length]

目前,client_write_IV和server_write_IV只能由[AEAD]3.2.1节中描述的隐式nonce技术生成。

实现注记:当前定义的密码协议族使用最多的是AES_256_CBC_SHA256。它需要2 x 32字节密钥和2 x 32字节MAC密钥,它们从128字节的密钥数据中产生。 7. TLS握手协议

TLS拥有3个子协议用于支持通信双方协商记录层安全参数,确认它们自己的身份,实例化以协商的安全参数,彼此报告错误状况。

握手协议负责协商一个会话,这个会话由以下元素组成:

session identifier

由Server端选取的一个任意字节的序列用于辨识一个活动的或可恢复的连接状态。

peer certificate

对端的X509v3 [PKIX]证书。这个元素的状态可以是空

compression method

在加密之前压缩数据的算法

cipher spec

指定用于产生密钥数据的伪随机函数(PRF),块加密算法(如:空,AES等),和MAC算法(如:HMAC-SHA1)。它也定义了密码学属性如mac_length.

(常见定义见附录A.6)

master secret

client和server之间共享的48字节密钥

is resumable

一个用于标识会话是否能被用于初始化新连接的标签

这些元素元素随后会被用于产生安全参数并由记录层在保护应用数据时使用。利用TLS握手协议的恢复特性,使用相同的会话可以实例化许多连接。 7.1 变更密码规范协议

变更密码规范协议存在的目的是通告加密策略的改变。这个协议由单个消息组成,这个消息会被在当前(并非挂起)连接状态下被加密和压缩。这个消息由一个值为1的单个字节构成。

struct {

enum { change_cipher_spec(1), (255) } type;

} ChangeCipherSpec;

ChangeCipherSpec消息可以被client和server发送,以通告接收方随后的记录消息将会由新协商的密码规范和密钥所保护。接收到这个消息会导致接收者指示记录层立即将读挂起状态复制到读当前状态。发送此消息后,发送这必须立即指示记录层将写挂起状态转变为写活动状态(见6.1节)。ChangeCipherSpec消息在握手过程中发送,但要在安全参数协商完毕之后,且在验证结束消息发送之前。

注意:如果发生一个重握手事件而连接的数据还在流动,通信的双方可能使用旧的CipherSpec继续发送数据。然而,一旦ChangeCipherSpec被发送了,新的CipherSpec必须被使用。先发送ChangeCipherSpec的一方不知道另外一方已经完成了新密钥数据的计算(例如,如果它执行一个耗时的公钥操作)。因此,在接收方必须缓存数据时会存在一个小的窗口时间。事实上,在现代机器中这个间隔会相当短。 7.2 报警协议

TLS记录层支持的内容类型之一是报警类型。报警消息传递了消息的严重程度(警告或致命)和报警的描述。致命类型的报警消息会导致连接的立即结束。在这种情况下,与会话对应的其它连接可以继续,但会话描述符必须非法化,以阻止失败的会话被用于建立新的连接。像其它消息一样,报警消息按照当前连接的状态指定的那样被加密和压缩。

enum { warning(1), fatal(2), (255) } AlertLevel;

enum {

close_notify(0),

unexpected_message(10),

bad_record_mac(20),

decryption_failed_RESERVED(21),

record_overflow(22),

decompression_failure(30),

handshake_failure(40),

no_certificate_RESERVED(41),

bad_certificate(42),

unsupported_certificate(43),

certificate_revoked(44),

certificate_expired(45),

certificate_unknown(46),

illegal_parameter(47),

unknown_ca(48),

access_denied(49),

decode_error(50),

decrypt_error(51),

export_restriction_RESERVED(60),

protocol_version(70),

insufficient_security(71),

internal_error(80),

user_canceled(90),

no_renegotiation(100),

unsupported_extension(110),

(255)

} AlertDescription;

struct {

AlertLevel level;

AlertDescription description;

} Alert; 7.2.1 关闭警报

客户端和服务器必须共享连接结束的信息以避免截断攻击每一方都可以发起关闭消息的交换

close_notify

这个消息通知接收者发送方在这条连接上将不再发送任何消息。像TLS 1.1一样,正常关闭一条连接失败不再要求一个会话不能恢复。这个变化从TLS 1.0开始并得到了应用实现的广泛遵守。

每一方都可以通过发送一个close_notify警报来发起一个关闭。在收到关闭警报后接收到任何数据都应该被忽略。

除非传递了一些其它致命类型的警报,每一方在关闭连接的写功能时都需要发送一个close_notify警报。另外一方必须以发送一个字节的close_notify作为回应,并立即关闭连接,丢弃任何挂起的写。不要求关闭的发起者在关闭连接的读功能前等待对端发送close_notify报警。

如果使用TLS的应用协议支持在TLS连接关闭之后在底层传输连接之上传输数据,则TLS实现必须在暗示应用层TLS已经结束之前收到对应的close_notify警报。如果应用协议不再传输任何额外的数据,而是仅仅关闭底层传输连接,则实现必须选择关闭连接而不再等待相应的close_notify警报。这个标准任何部分都不能被用于决定TLS管理其数据传输的使用模式,包括何时打开或关闭连接。 注意:假定在销毁传输之前关闭一个连接会可靠地投递挂起的数据。 7.2.2 错误警报

在TLS握手协议中错误处理非常简单。当探测到一个错误时,探测方发送一个消息给另一方。发送或接收到一个致命警报消息之后,双方立即关闭连接。服务端和客户端必须忘记任何会话描述符,密钥,和与一个失败的连接相关的机密信息。因此,任何一个被一个致命警报终结的连接都不能被恢复。

一个实现无论何时遭遇到一个被定义为致命警报的消息时,它必须在关闭者连接之前发送合适的警报。对于所有警报级别没有显式指定的错误,发送方可以自行决定是否将其作为致命错误对待。如果实现选择发送一个警报但意图随后立即关闭连接,它必须以致命警报等级发送这个警报。

如果收到一个警告级别的警报时,通常连接可以正常继续。如果接收方决定不再继续连接(例如,收到一个它不愿意接收的no_renegotiation警报),它应该发送一个致命警报终结连接。鉴于此,发送防通常不会知道接收方会有何行为。因此,警告类警报在发送方向继续连接时不是很有用,并且因此有时会被忽略。例如,如果一个对端决定接受一个证书过期警报(也许是在与用户确认之后)并想继续连接,它通常不会发送一个certificate_expired警报。

定义了如下类型的错误警报:

unexpected_message

收到一个不合适的消息。这个警报一直是致命类型且不应该在正确的实现之间被观察到。

bad_record_mac

如果收到一个记录其MAC是不正确的,则会返回这种警报。当因为一个TLSCiphertext以不正确的方式解密(或者它不是块长度的偶数倍,或它的填充值被检查出不正确)而发送一个警报时此类警报也必须发送。这个消息通常是致命类型并且不应该在正确的实现之间被观察到(除非消息在网络中损坏)。

decryption_failed_RESERVED

这种警报被用于一些早期版本的TLS,且可能导致针对CBC模式的特定攻击。在兼容型实现中不能发送此消息。

record_overflow

收到一个TLSCiphertext的消息其长度大于2^14+2048字节,或一个记录被解密为TLSCompressed后其长度大于2^14+1024字节。这个消息通常是致命类型并且不应该在正确的实现之间被观察到(除非消息在网络中损坏)。

decompression_failure

解压缩函数收到不正常的输入(例如, 数据扩展为过多的长度).这个消息通常是致命类型并且不应该在正确的实现之间被观察到(除非消息在网络中损坏)

handshake_failure

收到 handshake_failure警报消息意味着在给定选项时发送方不能协商一个可接受的安全参数集。这是个致命错误。

no_certificate_RESERVED

这个警报用于SSLv3但不是任何版本的TLS。在兼容型实现中不能发送此消息。

bad_certificate

证书被损坏,包含的签名无法正确验证等。

unsupported_certificate

证书是不支持的类型

certificate_revoked

证书被其签名者撤销

certificate_expired

证书过期或当前无效

certificate_unknown

在处理证书时产生了一些其它(未指定)问题,导致其无法接受

illegal_parameter

在握手阶段一个域的值超出合法范围或与其它的域不一致。这个消息一直是致命的。

unknown_ca

一个有效的证书链或部分链被接受,但证书没有被接受,因为CA证书不能被定位或不能与一个知名的、可信任的CA匹配。这个消息一直是致命的。

access_denied

接收到一个有效的证书, 但应用访问控制时, 发送方决定不继续协商. 这个消息一直是致命的。

decode_error

由于一些域超出指定范围或消息长度不正确导致消息不能被解码. 这个消息通常是致命类型的且绝不能在两个合理的TLS实现之间通信时出现(除非消息在网络中损坏)

decrypt_error

一个握手密文操作失败, 包括不能正确验证签名或一个结束消息. 这个消息一直是致命的。

export_restriction_RESERVED

这个alert曾被用于一些TLS的早期版本. 在兼容实现中不能发送此消息.

protocol_version

Client端试图协商的协议版本号版本被支持(例如, 旧的协议版本由于安全原因被废弃). 这个消息一直是致命的。

insufficient_security

当一个协商由于server需要比client能够支持的更安全的算法族而失败时取代handshake_failure消息返回, 这个消息一直是致命的.

internal_error

一个与对端或协议正确性都无关的内部错误(例如内存分配错误)使得协议无法继续执行.这个消息一直是致命的.

user_canceled

这次握手由于一些与协议错误无关的原因被取消.如果在握手完成后用户才取消操作,只通过发送一个close_notify消息来关闭连接更合适. 这个alert应该跟随一个close_notify. 这个消息通常是一个警告.

no_renegotiation

由客户端发送用于响应一个hello请求,或由服务端发送用于在初始化握手时响应一个client hello.二者中的任何一个都通常会导致重协商;当重协商不合适时, 接收端应答用此警报来响应.这时,原始请求者能决定是否继续连接.一种适当的情况是一个server产生一个进程以满足一个请求;这个进程能在启动时接收安全参数(密钥长度,认证等),在这个阶段之后再修改这些参数则可能会很困难.这个消息通常是一个警告.

unsupported_extension

由客户端发送;该客户端接收到了server hello消息中包含的一个扩展,但这些扩展不能被放入相应的client hello消息中.这个消息一直是致命的.

新的Alert的值在12章中由IANA指定. 7.3.

握手协议概览

会话状态的加密参数由TLS握手协议产生, 这个协议在TLS记录层之上运行. 当一个TLS client和server开始通信时, 它们就协议版本达成一致, 选择加密算法, 彼此选择验证, 使用公钥加密技术产生共享密钥.

TLS握手协议包含如下几步:

- 交互Hello消息以协商算法, 交互随机数, 检查会话恢复.

- 交互必要的密码参数以允许client和server协商预主密钥

- 交换证书和密码信息以允许client和server进行身份认证

- 从预主密钥和交互的随机数中产生主密钥

- 提为记录层供安全参数

- 允许client和server验证它们的对端已经计算出了相同的安全参数, 而且握手过程不被攻击者篡改.

需要注意的是更高层不能过度依赖TLS是否一直为连接双方协商最强的连接.中间人攻击者有很多方法能够使两个协商者降级到使用它们能够支持的最低的安全方法. 协议被用来最小化这种风险, 但仍然会存在攻击: 例如, 一个攻击者能够阻止访问一个安全服务运行的端口, 或试图使对端协商一个未经认证的连接. 基础的规则是更高层必须认识到它们的安全需求是什么, 且绝对不能在一个不满足它们的安全需求的通道上传输信息. 如果任何密码族能够提供它们承诺的安全等级则TLS协议是安全的: 如果你使用一个1024位的RSA密钥交换和验证过证书的主机来协商3DES, 你可以期待它是安全的.

这些目标的达成是靠握手协议实现的, 这个协议总结如下: client发送一个ClientHello消息, server必须回应一个ServerHello消息或产生一个验证错误并且连接会失败. ClientHello和ServerHello用于在client和server之间建立安全性增强的能力. ClientHello和ServerHello建立了如下的属性: 协议版本, 会话ID, 密码协议族, 压缩算法. 此外, 产生并交换两个随机数: ClientHello.random和ServerHello.random.

实际的密钥交换使用了最多4个消息: server Certificate, ServerKeyExchange, client Certificate,和ClientKeyExchange.新的密钥交换方法可以通过这些方法产生:为这些消息指定一个格式, 并定义这些消息的用法以允许client和server就一个共享密钥达成一致. 这个密钥必须很长;当前定义的密钥交换方法交换的密钥大于46字节.

在hello消息之后, server会在Certificate消息中发送它自己的证书, 如果它即将被认证. 此外, 一个ServerKeyExchange消息会被发送, 如果需要的话(例如, 如果server没有证书, 或者它的证书只用于签名).如果server被认证过了, 它可能会要求client发送证书, 如果对于已选择的密码协议族来说是合适的话.接下来, server会发送ServerHelloDone消息, 意味着握手的hello消息阶段完成. server将会等待client的响应. 如果server以及发送了一个CertificateRequest消息, client必须发送Certificate消息. 现在ClientKeyExchange消息需要发送, 这个消息的内容取决于ClientHello和ServerHello之间选择的公钥算法. 如果client发送了一个带签名能力的证书, 一个数字签名的CertificateVerify消息需要发送以显式验证证书中私钥的所有权.

这时, client发送一个ChangeCipherSpec消息, 并且复制待定的Cipher Spec到当前的Cipher Spec中. 然后client在新算法, 密钥确定后立即发送Finished消息. 作为回应, server会发送它自己的ChangeCipherSpec消息, 将待定的Cipher Spec转换为当前的Cipher Spec, 在新的Cipher Spec下发送Finished消息. 这时, 握手完成, client和server可以开始交换应用层数据(见下面的流程图). 应用数据一定不能在第一个握手完成前(在一个非TLS_NULL_WITH_NULL_NULL类型的密码协议族建立之前)发送.

Client

Server

ClientHello

-------->

ServerHello

Certificate*

ServerKeyExchange*

CertificateRequest*

<--------

ServerHelloDone

Certificate*

ClientKeyExchange

CertificateVerify*

[ChangeCipherSpec]

Finished

-------->

[ChangeCipherSpec]

<--------

Finished

Application Data

<------->

Application Data

Figure 1.

一个完整的握手消息流程

* 意味着可选或并不会一直被发送的条件依赖性消息.

注记: 为了有助于避免pipeline stalls, ChangeCipherSpec是一种独立的TLS协议内容类型, 并且事实上不是一种TLS消息.

当client和server决定继续一个以前的会话或复制一个现存的会话(取代协商新的安全参数)时, 消息流如下:

Client使用需要恢复的当前会话的ID发送一个ClientHello. Server检查它的会话缓存以进行匹配. 如果匹配成功, 并且server愿意在指定的会话状态下重建连接, 它将会发送一个带有相同会话ID值的ServerHello消息. 这时, client和server必须都发送ChangeCipherSpec消息并且直接发送Finished消息. 一旦重建立完成, client和server可以开始交换应用层数据(见下面的流程图). 如果一个会话ID不匹配, server会产生一个新的会话ID, 然后TLS client和server会完成一个完整的握手.

Client

Server

ClientHello

-------->

ServerHello

[ChangeCipherSpec]

<--------

Finished

[ChangeCipherSpec]

Finished

-------->

Application Data

<------->

Application Data

Figure 2.

一个简化的握手的消息流程

每个消息的内容和含义会在下面的章节中详细阐述. 7.4.

握手协议

TLS握手协议是TLS记录协议的一个已定义的高层客户端. 这个协议用于协商一个会话的安全属性. 握手消息提供给TLS记录层, 这里它们会被封装在一个或多个TLSPlaintext结构中, 这些结构按照当前活动会话状态所指定的方式被处理和传输.

enum {

hello_request(0), client_hello(1), server_hello(2),

certificate(11), server_key_exchange (12),

certificate_request(13), server_hello_done(14),

certificate_verify(15), client_key_exchange(16),

finished(20), (255)

} HandshakeType;

struct {

HandshakeType msg_type;

/* handshake type */

uint24 length;

/* bytes in message */

select (HandshakeType) {

case hello_request:

HelloRequest;

case client_hello:

ClientHello;

case server_hello:

ServerHello;

case certificate:

Certificate;

case server_key_exchange: ServerKeyExchange;

case certificate_request: CertificateRequest;

case server_hello_done:

ServerHelloDone;

case certificate_verify:

CertificateVerify;

case client_key_exchange: ClientKeyExchange;

case finished:

Finished;

} body;

} Handshake;

握手协议消息在下文中会以发送的顺序展现;以未期望的顺序发送握手消息会导致一个致命错误。然而,不需要的握手消息会被忽略。需要注意的是例外的顺序是:证书消息在握手(从server到client,然后从client到server)过程中会使用两次,但只在它第一次出现的位置处描述。不被这些顺序所约束的一个消息是HelloRequest消息,它可以在任何时间发送,但如果在握手中间到达应该被client忽略。

新的握手消息类型由IANA指定,正如12节中所描述的。 7.4.1.

Hello消息

hello阶段的消息用于在client和server之间交互安全增强能力。当一个新的会话开始时,记录层连接状态加密,hash,和压缩算法被初始化为空。当前连接状态用于重协商消息。 7.4.1.1.

Hello Request

当这个消息即将被发送时:

HelloRequest消息可以在任何时间由server发送。

这个消息的含义:

HelloRequest是一个简单的通告client应该开始重协商流程。 在响应过程中,client应该在方便的时候发送一个ClientHello消息。这个消息并不是意图确定哪端是client或server,而仅仅是发起一个新的协商。server不应该在client发起连接后立即发送一个HelloRequest。这时发送一个ClientHello消息是client的工作。

如果client当前正在协商一个会话时这个消息会被client忽略。 这个消息会被client忽略,如果client不想重新协商一个会话,或client希望响应一个no_renegotiation警报。因为握手消息意图先于应用数据被传送, 它希望协商会在少量记录消息被client接收之前开始。 如果server发送了一个HelloRequest但没有收到一个ClientHello响应,它应该用一个致命警报关闭连接。

在发送一个HelloRequest之后, server不应该重复这个请求直到随后的握手协商完成。

这个消息的结构:

struct { } HelloRequest;

这个消息不能被包含在握手消息中维护的消息hash中, 也不能用于结束的消息和证书验证消息。 7.4.1.2.

Client Hello 当这个消息被发送时:

当一个client第一次连接一个server时, 它被要求发送ClientHello作为第一个消息。 Client也能发送一个ClientHello作为对HelloRequest的响应,或用于自身的初始化以便在一个已有连接中重新协商安全参数。

这个消息的结构是:

ClientHello消息包含一个随机数据结构, 它会随后被用于协议中.

struct {

uint32 gmt_unix_time;

opaque random_bytes[28];

} Random;

gmt_unix_time 依据发送者内部时钟以标准UNIX 32位格式表示的当前时间和日期(从1970年1月1日UTC午夜开始的秒数, 忽略闰秒). 基本TLS协议不要求时钟被正确设置; 更高层或应用层协议可以定义额外的需求. 需要注意的是, 出于历史原因, 数据元素使用格林尼治时间命名, 它是当前时间通用时间基准世界协调时间(UTC)的前任.

random_bytes

由一个安全的随机数生成器产生的28个字节数据.

ClientHello消息包含一个变长的会话标识符. 如果非空, 这个值标识了同一对client和server之间的会话, client希望重新使用这个会话中server的安全参数.

会话标识符可能来自于一个早期的连接, 本次连接, 或来自另一个当前活动的连接. 第二个选择是有用的, 如果client只是希望更新随机数据结构并且从一个连接中导出数值; 第三个选择使得在无需重复全部握手协议的情况下就能够建立若干独立的安全连接.这些独立的连接可能先后或同时建立. 一个SessionID在握手协商完成成为有效的, 通过交互Finished消息得以完善, 并坚持到由于老化或在一个与会话有个的连接上遭遇致命错误导致它被删除为止. SessionID的实际内容由server定义.

opaque SessionID<0..32>;

警告: 由于SessionID在传输时没有加密或直接的MAC保护, server一定不能将机密信息放在会话标识符中或使伪造的会话标识符的内容违背安全原则.(需要注意的是握手的内容作为一个整体, 包括SessionID, 是由在握手结束时交换的Finished消息保护的).

密码族列表, 在ClientHello消息中从client传递到server, 以client所倾向的顺序(最喜爱的在最先)包含了client所支持的密码算法. 每个密码族定义了一个密钥交互算法, 一个块加密算法(包括密钥长度), 一个MAC算法, 和一个随机数生成函数. Server将选择一个密码协议族, 如果没有可以接受的选择, 在返回一个握手失败警报然后关闭连接. 如果列表包含了server不能识别, 支持或希望使用的密码协议族, server必须忽略它们, 并正常处理其余的部分.

uint8 CipherSuite[2];

/* Cryptographic suite selector */

ClientHello保护了client所支持的压缩算法列表, 按照client的倾向排序.

enum { null(0), (255) } CompressionMethod;

struct {

ProtocolVersion client_version;

Random random;

SessionID session_id;

CipherSuite cipher_suites<2..2^16-2>;

CompressionMethod compression_methods<1..2^8-1>;

select (extensions_present) {

case false:

struct {};

case true:

Extension extensions<0..2^16-1>;

};

} ClientHello;

TLS 允许在compression_methods域之后的extensions块中添加扩展. 通过查看compression_methods后面是否有多余的字节在ClientHello结尾处就能探测到扩展的存在. 需要注意的是这种探测可选数据的方法与正常的TLS变长域不一样, 但它用于与TLS一起定义的扩展兼容.

client_version

client愿意在本次会话中使用的TLS协议的版本. 这个应当是client所能支持的最新版本(值最大), 对于本规范来说, 这个版本应该是3.3(关于后向兼容性的细节见附录E).

random

一个client所产生的随机结构.

session_id

Client希望在本次连接中所使用的会话ID. 如果没有session_id或client想生成新蛋安全参数, 则这个域是空.

cipher_suites

Client所支持的密码选项列表, client最倾向使用的排在最先. 如果session_id域不空(意味着一个会话恢复请求), 这个向量必须至少包含那条会话中的cipher_suite. 数值在附录A.5中定义.

compression_methods

这是client所支持的压缩算法的列表, 按照client所倾向的顺序排列. 如果session_id域不空(意味着一个会话恢复请求), 它必须包含那条会话中的compression_method.这个向量中必须包含, 所有的实现也必须支持CompressionMethod.null. 因此, 一个client和server将一直能就压缩算法协商一致.

extensions

Clients可以通过在扩展域中发送数据来请求server的扩展功能. 实际的"Extension"的格式在7.4.1.4节中定义.

如果一个client使用扩展来请求额外的功能, 并且这个功能Server并不支持, 则client可以中止握手. 一个server必须接受带有或不带扩展域的ClientHello消息, 并且(对于其它所有消息也是一样)它必须检查消息中数据的数量是否精确匹配一种格式; 如果不是, 它必须发送一个致命"decode_error"警报.

发送ClientHello消息之后, client会等待ServerHello消息. Server返回的任何握手消息, 除HelloRequest外, 均被作为一个致命错误. 7.4.1.3. Server Hello

当这个消息被发送时:

Server将发送这个消息作为对ClientHello消息的响应, 当它能够找到一个可接受的算法集时. 如果不能找到这样的算法集, 它会发送一个握手失败警报作为响应.

这个消息的结构是:

struct {

ProtocolVersion server_version;

Random random;

SessionID session_id;

CipherSuite cipher_suite;

CompressionMethod compression_method;

select (extensions_present) {

case false:

struct {};

case true:

Extension extensions<0..2^16-1>;

};

} ServerHello;

通过查看compression_methods后面是否有多余的字节在ServerHello结尾处就能探测到扩展的存在.

server_version

这个域将包含client在client hello消息中建议的较低版本和server所能支持的最高版本. 本规范中的版本是3.3. (关于后向兼容性的细节见附录E)

random

这个结构由server产生并且必须独立于ClientHello.random.

session_id

这是与本次连接相对应的会话的标识. 如果ClientHello.session_id非空, server将在它的会话缓存中进行匹配查询. 如果匹配项被找到, 且server愿意使用指定的会话状态建立新的连接, server会将与client所提供的相同的值反馈回去. 这意味着恢复了一个会话并且规定双方必须在Finished消息之后继续进行通信. 否则这个域会包含一个不同的值以标识新会话. Server会返回一个空的session_id以显示会话将不再被缓存从而不会被恢复. 如果一个会话被恢复了, 它必须使用原来所协商的密码协议族. 需要注意的是没有要求server恢复任何会话, 即使它之前提供了一个session_id.Client必须准备好进行一次完整的协商 -- 包括协商新的密码协议族-- 在任意一次握手中.

由server在ClientHello.cipher_suites中所选择的单个密码族. 对于被恢复的会话, 这个域的值来自于被恢复的会话状态.

compression_method

由server在ClientHello.compression_methods所选择的单个压缩算法. 对于被恢复的会话, 这个域的值来自于被恢复的会话状态.

extensions

扩展的列表. 需要注意的是只有由client给出的扩展才能出现在server的列表中. 7.4.1.4. Hello Extensions

扩展的格式为:

struct {

ExtensionType extension_type;

opaque extension_data<0..2^16-1>;

} Extension;

enum {

signature_algorithms(13), (65535)

} ExtensionType;

这里:

-

"extension_type" 表示特定的扩展类型.

-

"extension_data" 包含的信息却决于特定的扩展类型.

扩展的初始集合在一个文档[TLSEXT]中定义.The initial set of extensions is defined in a companion document[TLSEXT].

扩展类型列表由INAM赋值维护,详见12节.

一个扩展不能出现在ServerHello中除非同样的扩展类型出现在对于的ClientHello中. 如果一个client在ServerHello中收到一个扩展类型但在相关的ClientHello中并没有请求, 它必须用一个unsupported_extension致命警报来丢弃握手.

尽管如此, "面向server"的扩展将来可以在这个框架中提供. 这样的一个扩展(比如, 类型x的扩展)可能要求client首先发送一个类型x的扩展在ClientHello中, 并且extension_data为空以表示它支持扩展类型. 在这个例子中, client提供了理解扩展类型的能力, server基于client提供的内容与其进行通信.

当ClientHello或ServerHello中有多个不同类型的扩展存在时, 这些扩展可能会以任意顺序出现. 一个类型不能拥有超过一个扩展.

最后, 需要注意的是扩展可能在开始一个新会话和要求恢复一个会话是发送. 的确, 一个请求恢复会话的client通常不会知道server是否会接受这个请求, 因此它应该像它不打算回复会话时那样发送扩展.

通常, 每个扩展类型的规范需要描述扩展对全部握手流程和会话恢复的影响. 大多数当前的TLS扩展仅当一个会话被初始化时才是相关联的: 当一个旧的会话被恢复时, server不会处理Client Hello中的扩展, 也不会将其包含在Server Hello中. 然而, 一些扩展可以在会话恢复时指定不同的行为.

在这个协议的新特性与现存特性之间会有一些敏感(以及不很敏感)的交互产生, 这可能会导致整体安全性的显著降低. 当设计新的扩展时应考虑下列事项: -

一些情况下一个server没有就一个扩展协商一致是错误情况,

一些情况下则简单地拒绝支持特定特性. 通常,错误警报应该用于前者, server扩展中的一个域用于响应后者. -

扩展应该尽可能在设计上阻止任何通过操纵握手消息来强制使用(或不使用)一个特殊特性进行的攻击. 无论这个特性是否被确认会导致安全问题, 这个原则都应该被遵循.

通常扩展域扩展域都会被包含在Finished消息的hash输入中, 但需要给予极大关注的是在握手阶段扩展改变了发送消息的含义. 设计者和实现者应该注意的事实是, 握手被认证后, 活动的攻击者才能修改消息并插入, 移动或替换扩展.

-

使用扩展来改变TLS设计的主要方面在技术上是可能的; 例如密码族协商的设计. 这种做法并不被推荐; 更合适的做法是定义一个新版本的TLS -- 尤其是TLS握手算法有特定的保护方法以防御基于版本号的版本回退攻击, 版本回退攻击的可能性应该在任何主要的修改设计中都是一个有意义的考量. 7.4.1.4.1. 签名算法

Client使用"signature_algorithms"扩展来向server表明哪个签名/hash算法对会被用于数字签名. 这个扩展的"extension_data"域包含了一个"supported_signature_algorithms"值.

enum {

none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),

sha512(6), (255)

} HashAlgorithm;

enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) }

SignatureAlgorithm;

struct {

HashAlgorithm hash;

SignatureAlgorithm signature;

} SignatureAndHashAlgorithm;

SignatureAndHashAlgorithm

supported_signature_algorithms<2..2^16-2>;

每个SignatureAndHashAlgorithm值都列出了一个client愿意使用的hash/签名对.这些值根据倾向使用的程度按降序排列.

注: 由于并不是所有的签名算法和hahs算法都会被一个实现所接受(例如: DSA接受SHA-1, 不接受SHA-256), 所有算法是按对列出.

hash 这个域表明可能使用的hash算法. 这些值分别表明支持无hash, MD5, SHA-1, SHA-224, SHA-256, SHA-384, 和SHA-512. "none"值用于将来的可扩展性, 以防一个签名算法在签名之前不需要hash.

signature

这个域表明使用哪个签名算法. 这些值分别表示匿名签名, RSASSA-PKCS1-v1_5, DSA和ECDSA. "anonymous"值在这个上下文中是无意义的, 但会在7.4.3节中使用. 它不能出现在这个扩展之中.

这个扩展的语义某种程度上有些复杂, 因为密码族表明允许的签名算法而不是hash算法. 7.4.2和7.4.3节描述了合适的规则.

如果client只支持默认的hash和签名算法(本节中所列出的), 它可以忽略signature_algorithms扩展. 如果client不支持默认的算法, 或支持其它的hash和签名算法(并且它愿意使用他们来验证server发送的消息, 如:server certificates和server key exchange), 它必须发送signature_algorithms扩展, 列出它愿意接受的算法.

如果client不发送signature_algorithms扩展, server必须执行如下动作:

-

如果协商后的密钥交换算法是(RSA, DHE_RSA,DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA)中的一个, 处理行为同client发送了 {sha1,rsa};

-

如果协商后的密钥交换算法是(DHE_DSS, DH_DSS)中的一个, 处理行为同client发送了{sha1,dsa}.

-

如果协商后的密钥交换算法是(ECDH_ECDSA,ECDHE_ECDSA)中的一个, 处理行为同client发送了{sha1,ecdsa}.

注: 这个对于TLS 1.1是一个变更, 且没有显式的规则, 但在实现上可以假定对端支持MD5和SHA-1.

注: 这个扩展对早于1.2版本的TLS是没有意义的, 对于之前的版本Client不能这个扩展. 然而, 即使client提供了这个扩展, [TLSEXT]中明确的规则要求server如果不能理解扩展则忽略之.

Server不能发送此扩展, TLS server必须支持接收此扩展.

当进行会话恢复时, 这个扩展不能被包含在Server Hello中, 且server会忽略Client Hello中的这个扩展(如果有). 7.4.2. Server证书

当这个消息即将被发送时:

server必须发送一个Certificate,无论何时协商一致的密钥交换算法使用证书进 行认证(包括除DH_anon外本文中定义的所有密 钥交互算法)。 这个消息一直是 紧随在ServerHello消息之后。

这个消息的含义是:

这个消息传达了server的证书链给client.

证书必须适用于已协商的密码族的密钥交互算法和任何已协商的扩展。

这个消息的结构是:

opaque ASN.1Cert<1..2^24-1>;

struct {

ASN.1Cert certificate_list<0..2^24-1>;

} Certificate;

certificate_list

这是一个证书序列(链)。发送者的证书必须在列表的第一个位置。每个随后的证 书必须直接证明它前面的证书。 因为证书验证需要被独立发布的根密钥, 确定了 根证书权威的自签名证书可以被在链中忽略, 如果远程终端已经拥有了它以便在 任何情况下来验证它。

相同的消息类型和结果将用于client端对一个证书请求消息的响应. 需要注意的是一个client可能不发送证书, 如果它没有合适的证书来发送以响应server的认证请求.

注: PKCS #7不会被用做证书向量的格式因为PKCS #6扩展的证书没有被使用. 同样, PKCS #7定义了一个集合而非一个序列, 使得解析列表的任务变得更难完成.

如下的规则会被应用于server发送的证书:

-

证书类型必须是X.509v3, 除非显式协商了其它的类型(如, [TLSPGP]).

-

终端实体证书的公钥(和相关的限制)必须与选择的密钥交互算法兼容.

密钥交换算法.

证书类型

RSA

RSA公钥; 证书必须允许密钥用于加密(keyEncipherment位必须被设置, 如果密钥使用扩展存在的话).

RSA_PSK

注:RSA_PSK 定义于[TLSPSK]

DHE_RSA

RSA公钥;证书必须允许密钥使用server密钥交互消息中的签名机制和hash算法进行签名

ECDHE_RSA

(如果密钥用法扩展存在的话,digitalSignature位必须设置)

注: ECDHE_RSA定义于[TLSE