RFC 6347 - 4.1. 记录层 (Record Layer)
4. Differences from TLS (与 TLS 的差异)
如第 3 节所述, DTLS 有意与 TLS 非常相似。因此, 我们不把 DTLS 作为全新协议呈现, 而是作为相对 TLS 1.2 [TLS12] 的一系列增量。凡未明确说明差异之处, DTLS 与 [TLS12] 相同。
4.1. Record Layer (记录层)
DTLS 记录层与 TLS 1.2 的记录层极为相似。唯一变化是在记录中加入显式序列号 (explicit sequence number)。该序列号使接收方能够正确校验 TLS MAC (Message Authentication Code)。DTLS 记录格式如下所示:
struct {
ContentType type;
ProtocolVersion version;
uint16 epoch; // New field
uint48 sequence_number; // New field
uint16 length;
opaque fragment[DTLSPlaintext.length];
} DTLSPlaintext;
type (类型)
与 TLS 1.2 记录中的 type 字段等价。
version (版本)
所采用协议版本。本文档描述 DTLS 1.2 版, 使用版本 { 254, 253 }。254.253 这一版本值是 DTLS 1.2 版本号的一的补码 (1's complement)。TLS 与 DTLS 版本号之间的最大间隔确保两种协议的记录易于区分。应注意, DTLS 未来的线上版本号递减 (而真实版本号递增)。
epoch (纪元)
在每次密码状态改变时递增的计数器值。
sequence_number (序列号)
本记录的序列号。
length (长度)
与 TLS 1.2 记录中的 length 字段相同。与 TLS 1.2 一样, 长度不应超过 2^14。
fragment (片段)
与 TLS 1.2 记录的 fragment 字段相同。
DTLS 使用显式序列号, 而非隐式序列号, 序列号承载于记录的 sequence_number 字段。序列号按各 epoch 分别维护, 每个 epoch 的 sequence_number 初值为 0。例如, 若来自 epoch 0 的握手消息被重传, 其序列号可能晚于 epoch 1 的消息, 即便 epoch 1 的消息先发送。应注意在握手期间需格外小心, 确保重传消息使用正确的 epoch 与密钥材料。
若在较短时间内执行多次握手, 线上可能出现多条具有相同序列号但来自不同密码状态的记录。epoch 字段使接收方能够区分此类分组。epoch 号初始为零, 每次发送 ChangeCipherSpec 消息时递增。为确保任意给定的序列号/epoch 对唯一, 实现禁止 (MUST NOT) 在小于两倍 TCP 最大段生存期 (maximum segment lifetime) 的时间间隔内复用同一 epoch 值。实践中 TLS 实现很少重握手; 因此我们预期这不是问题。
注意由于 DTLS 记录可能重排, 在 epoch 2 开始后仍可能收到来自 epoch 1 的记录。一般而言, 实现应当 (SHOULD) 丢弃来自较早 epoch 的分组, 但若分组丢失造成明显问题, 它们可以 (MAY) 选择为允许分组重排在至多 TCP [TCP] 规定的默认 MSL 时间内保留先前 epoch 的密钥材料。(注意此处意图是实现者采用 IETF 当前对 MSL 的指导, 而非试图探查系统 TCP 栈所使用的 MSL。) 在握手完成之前, 实现必须 (MUST) 接受来自旧 epoch 的分组。
反之, 受新协商上下文保护的记录也可能在握手完成之前到达。例如, 服务器可发送其 Finished 消息然后开始传输数据。实现可以 (MAY) 缓冲或丢弃此类分组, 但当 DTLS 运行于可靠传输 (例如 SCTP) 之上时, 应当 (SHOULD) 缓冲并在握手完成后处理。注意 TLS 关于何时允许发送分组的限制仍然适用, 接收方将这些分组视同按正确顺序发送。特别地, 在首次握手完成之前仍然禁止发送数据。
注意在现有关联上重握手的特殊情形下, 即便尚未收到 ChangeCipherSpec 或 Finished 消息, 也可以立即处理数据分组, 前提是重握手恢复现有会话, 或使用与现有关联完全相同的安全参数。在任何其他情形下, 实现必须 (MUST) 等待收到 Finished 消息以防止降级攻击 (downgrade attack)。
与 TLS 一样, 实现必须 (MUST) 在允许序列号回绕之前放弃关联或进行重握手。
同样, 实现禁止 (MUST NOT) 允许 epoch 回绕, 而必须 (MUST) 建立新关联并按第 4.2.8 节所述终止旧关联。实践中实现很少在同一信道上反复重握手, 因此这不太可能成为问题。
4.1.1. Transport Layer Mapping (传输层映射)
每条 DTLS 记录必须 (MUST) 放入单个数据报内。为避免 IP 分片, DTLS 记录层的客户端应当 (SHOULD) 尝试将记录大小调整为适合从记录层获得的任意 PMTU 估计值。
注意与 IPsec 不同, DTLS 记录不包含任何关联标识符。应用必须安排在不同关联之间复用。对于 UDP, 推测通过主机/端口号完成。
多个 DTLS 记录可置于同一数据报中。它们仅连续编码。DTLS 记录成帧足以确定边界。但应注意, 数据报载荷的第一个字节必须是一条记录的起始。记录不得跨越数据报。
某些传输 (如 DCCP [DCCP]) 提供自身的序列号。当承载于这些传输之上时, DTLS 与传输层序列号将同时存在。尽管这会引入少量低效, 传输层与 DTLS 序列号用途不同; 因此为概念清晰起见, 同时使用两者更优。未来可规定 DTLS 扩展以在受限环境中仅使用一套序列号。
某些传输 (如 DCCP) 对承载于其上的流量提供拥塞控制。若拥塞窗口足够窄, DTLS 握手重传可能被暂缓而非立即发送, 可能导致超时与伪重传。当 DTLS 用于此类传输时, 应注意不要冲过可能的拥塞窗口。[DCCPDTLS] 定义了考虑这些问题的 DTLS 到 DCCP 映射。
4.1.1.1. PMTU Issues (PMTU 问题)
一般而言, DTLS 的理念是将 PMTU 发现留给应用。然而, DTLS 不能完全忽视 PMTU, 原因有三:
-
DTLS 记录成帧会增大数据报大小, 从而降低应用视角下的有效 PMTU。
-
在某些实现中, 应用可能不直接与网络对话, 此时 DTLS 栈可能吸收 ICMP [RFC1191] "Datagram Too Big" 指示或 ICMPv6 [RFC4443] "Packet Too Big" 指示。
-
DTLS 握手消息可能超过 PMTU。
为处理前两个问题, DTLS 记录层应当 (SHOULD) 按下述方式行为。
若底层传输协议可提供 PMTU 估计, 应将其提供给上层协议。特别是:
-
对于基于 UDP 的 DTLS, 上层协议应当 (SHOULD) 被允许获取 IP 层维护的 PMTU 估计。
-
对于基于 DCCP 的 DTLS, 上层协议应当 (SHOULD) 被允许获取当前 PMTU 估计。
-
对于基于 TCP 或 SCTP 的 DTLS, 其自动分片并重组数据报, 不存在 PMTU 限制。然而, 上层协议禁止 (MUST NOT) 写入超过最大记录大小 2^14 字节的任何记录。
DTLS 记录层应当 (SHOULD) 允许上层协议发现 DTLS 处理预期的记录扩展量。注意该数字仅为估计, 因为存在块填充与可能使用 DTLS 压缩。
若存在传输协议指示 (通过 ICMP 或如 [DCCP] 第 14 节所述拒绝发送数据报), 则 DTLS 记录层必须 (MUST) 将错误通知上层协议。
DTLS 记录层不应 (SHOULD NOT) 干扰上层协议执行 PMTU 发现, 无论是通过 [RFC1191] 还是 [RFC4821] 机制。特别是:
-
在底层传输允许之处, 上层协议应当 (SHOULD) 被允许设置 DF 位状态 (IPv4) 或禁止本地分片 (IPv6)。
-
若底层传输允许应用请求 PMTU 探测 (例如 DCCP), DTLS 记录层应遵从该请求。
最后一个问题是 DTLS 握手协议。从 DTLS 记录层视角, 它只是另一种上层协议。然而, DTLS 握手不频繁且仅涉及少量往返; 因此握手协议的 PMTU 处理更重视快速完成而非精确 PMTU 发现。为在这些情形下仍允许连接, DTLS 实现应当 (SHOULD) 遵循以下规则:
-
若 DTLS 记录层告知 DTLS 握手层某消息过大, 它应当 (SHOULD) 立即尝试使用关于 PMTU 的已有信息对其进行分片。
-
若反复重传仍未得到响应且 PMTU 未知, 后续重传应当 (SHOULD) 退回到更小的记录尺寸, 并酌情对握手消息分片。本标准未规定退避前应尝试重传的确切次数, 但 2-3 次似乎合适。
4.1.2. Record Payload Protection (记录载荷保护)
与 TLS 一样, DTLS 以一系列受保护记录传输数据。本节其余部分描述该格式的细节。
4.1.2.1. MAC
DTLS 的 MAC 与 TLS 1.2 相同。然而, 计算 MAC 时使用的序列号不是 TLS 的隐式序列号, 而是由 epoch 与线上顺序出现的序列号拼接形成的 64 位值。注意 DTLS 的 epoch + 序列号长度与 TLS 序列号相同。
TLS MAC 计算以协议版本号为参数; 对于 DTLS, 此为线上版本, 即 DTLS 1.2 的 {254, 253}。
注意 DTLS 与 TLS 在 MAC 处理方面的一个重要差异: 在 TLS 中, MAC 错误必须导致连接终止。在 DTLS 中, 接收实现可以 (MAY) 仅丢弃有问题记录并继续连接。此变化可行是因为 DTLS 记录不像 TLS 记录那样相互依赖。
一般而言, DTLS 实现应当 (SHOULD) 静默丢弃 MAC 错误或以其他方式无效的记录。它们可以 (MAY) 记录错误。若 DTLS 实现在收到无效 MAC 的消息时选择生成告警 (alert), 它必须 (MUST) 生成级别为 fatal 的 bad_record_mac 告警并终止其连接状态。注意由于错误不会导致连接终止, DTLS 栈作为错误类型预言机 (oracle) 比 TLS 栈更高效。因此, 尤其应遵循 [TLS12] 第 6.2.3.2 节的建议。
4.1.2.2. Null or Standard Stream Cipher (空或标准流密码)
DTLS NULL 密码的执行与 TLS 1.2 NULL 密码完全相同。
TLS 1.2 描述的唯一流密码是 RC4, 其无法随机访问。RC4 禁止 (MUST NOT) 与 DTLS 联用。
4.1.2.3. Block Cipher (分组密码)
DTLS 分组密码的加密与解密与 TLS 1.2 完全相同。
4.1.2.4. AEAD Ciphers (AEAD 密码)
TLS 1.2 引入了带附加数据的认证加密 (authenticated encryption with additional data, AEAD) 密码套件。在 [ECCGCM] 与 [RSAGCM] 中定义的现有 AEAD 密码套件可与 DTLS 联用, 方式与 TLS 1.2 相同。
4.1.2.5. New Cipher Suites (新密码套件)
注册时, 新的 TLS 密码套件必须 (MUST) 标明是否适用于 DTLS 以及需要何种适配 (见第 7 节 IANA 考量)。
4.1.2.6. Anti-Replay (反重放)
DTLS 记录包含序列号以提供重放保护。序列号验证应当 (SHOULD) 使用下述滑动窗口过程, 借自 [ESP] 第 3.4.3 节。
本会话的接收分组计数器在会话建立时必须 (MUST) 初始化为零。对每条收到的记录, 接收方必须 (MUST) 验证记录包含的序列号不与本会话生存期内收到的任何其他记录重复。这应当 (SHOULD) 是在分组匹配到会话后施加的第一项检查, 以加快拒绝重复记录。
通过滑动接收窗口拒绝重复。(窗口如何实现是本地事务, 但下文描述实现必须表现出的功能。) 最小窗口大小 32 必须 (MUST) 支持, 但窗口大小 64 更受偏好且应当 (SHOULD) 作为默认采用。接收方可选择 (MAY) 另一窗口大小 (大于最小值)。(接收方不通知发送方窗口大小。)
窗口的 "右" 沿表示本会话上收到的最高已验证序列号。包含低于窗口 "左" 沿序列号的记录被拒绝。落入窗口内的分组对照窗口内已收分组列表检查。基于位掩码执行该检查的高效方法见 [ESP] 第 3.4.3 节。
若收到的记录落在窗口内且为新, 或分组在窗口右侧, 则接收方继续进行 MAC 验证。若 MAC 验证失败, 接收方必须 (MUST) 将收到的记录作为无效丢弃。仅当 MAC 验证成功时才更新接收窗口。
4.1.2.7. Handling Invalid Records (处理无效记录)
与 TLS 不同, DTLS 面对无效记录 (例如无效格式、长度、MAC 等) 具有韧性。一般而言, 无效记录应当 (SHOULD) 被静默丢弃, 从而保留关联; 然而, 可以 (MAY) 为诊断目的记录错误。若实现选择改为生成告警, 则必须 (MUST) 生成 fatal 级别告警, 以避免攻击者反复探测实现对各种错误的响应。注意若 DTLS 运行于 UDP 之上, 任何这样做的实现将极易遭受拒绝服务 (DoS) 攻击, 因为 UDP 伪造非常容易。因此, 对此类传输不建议 (NOT RECOMMENDED) 采用该做法。
若 DTLS 承载于可抵抗伪造的传输之上 (例如带 SCTP-AUTH 的 SCTP), 则发送告警更安全, 因为攻击者将难以伪造不会被传输层拒绝的数据报。