ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

OpenSSL 抽象记录层(Abstract Record Layer)设计解析:从 TLS/DTLS/KTLS 到 QUIC 的可插拔记录层架构

OpenSSL 抽象记录层(Abstract Record Layer)设计解析:从 TLS/DTLS/KTLS 到 QUIC 的可插拔记录层架构 OpenSSL 抽象记录层Abstract Record Layer设计解析从 TLS/DTLS/KTLS 到 QUIC 的可插拔记录层架构【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl本文围绕 OpenSSL 仓库中 记录层设计文档 展开深入讲解 libssl 如何通过OSSL_RECORD_METHOD与OSSL_RECORD_LAYER两个核心抽象将长期散落在代码各处的 TLS、DTLS、KTLS 记录层实现统一为可插拔pluggable的架构并为 QUIC-TLS 记录层QUIC CRYPTO frames的引入铺平道路。读完本文你将掌握记录层的读写协议、重试语义、状态查询与事件回调机制并能对照 include/internal/recordmethod.h 等真实源码理解该接口从设计草案到落地实现的演进差异。设计背景为什么需要抽象记录层在 (D)TLS 协议体系中记录record指一个数据包通常包含头部header与负载payload并经常经过加密保护。一个记录可能与网络报文一一对应也可能不对应这取决于具体记录层的实现细节。record 一词直接源自 TLS 与 DTLS 规范。libssl 需要支持多种记录层类型与变体记录层类型说明标准 TLS 记录层面向 TCP 的 TLS 1.0/1.1/1.2/1.3 记录格式标准 DTLS 记录层面向不可靠数据报的 DTLS 记录格式含 epoch、sequence number、bitmap 防重放Kernel TLSKTLS记录层将加解密下沉到内核协议栈的加速路径QUIC-TLS 记录层规划中QUIC 内部的 inner TLS 实现记录形态为 QUIC CRYPTO frames在 TLS 记录层内部还存在multiblock与pipelining两种优化选项分别对应一次读写多条记录的不同处理方式所有记录层变体还必须能够处理不同的协议版本。这些实现是在多年间、不同时期陆续加入的各自为当时的目标采用了略有差异的实现策略接入点散布在整个代码库中这一点可以在 ssl/record/methods/ 下的多个实现文件以及 ssl/record/rec_layer_s3.c、ssl/record/rec_layer_d1.c 中直观看到。QUIC 支持的引入将带来一种全新的记录层实现即 QUIC-TLS 记录层此时若继续沿用散乱接入的方式维护成本将急剧上升这正是抽象记录层设计的直接动因。需求来自 QUIC 技术需求文档的约束技术需求文档 quic-requirements.md 列出了与记录层直接相关的需求可插拔记录层接口现有 libssl 记录层包含 TLS、DTLS 和 KTLS 支持QUIC 将引入另一个变体未来可能还有更多。OMC 要求实现可插拔记录层接口以降低侵入性、提高可维护性并统一 TLS、DTLS、KTLS 与规划中的 QUIC 协议之间现有的记录层交互。MVP 阶段该接口仅限内部使用internal only未来版本才公开。MVP 目标下一版本的 MVP 是一个可插拔记录层接口以及一个以s_client形式提供的单流 QUIC 客户端且不需要显著的 API 变更。在 MVP 中互操作性interoperability优先于严格的标准合规性。后续版本目标一旦 QUIC 实现功能完整外部库应能够使用该可插拔记录层接口并通过 provider 提供稳定的 ABI。候选方案对比METHOD 方案 vs Provider 方案设计文档评估了两种截然不同的实现思路。方案一基于 METHOD 的接口METHOD 方案本质上是一个包含函数指针的结构体这是 OpenSSL 代码库中的常见模式。不同实现策略的差异对 METHOD 的调用方完全隐藏。在该方案中为每种受支持的记录层类型实现一个不同的 METHOD标准 TLS 一个、标准 DTLS 一个、内核 TLS 一个、QUIC-TLS 一个。MVP 阶段 METHOD 保持私有一旦稳定可以轻松提供公共函数让最终用户应用构造自己的 METHOD。优点简单且是 OpenSSL 历史上一贯使用的方案可作为最终公共解决方案的基础也可作为后续版本中可 fetch可获取方案的基础若后续转为可 fetch 方案大部分相关工作量可以推迟到后续版本。缺点与 OpenSSL 3.0 中用于可扩展性的 provider 方案不一致若实现后又转为可 fetch 方案可能需要返工。方案二基于 Provider 的方案该方案与 METHOD 方案非常相似核心区别在于记录层实现存放在 provider 中并以类似 OpenSSL 3.0 中密码学算法 fetch 的方式获取fetch。这更契合 3.0 确立的可扩展性方向——METHOD 正在被弃用provider 被广泛使用。但存在一个硬性限制复杂对象如SSL对象不能跨 libssl/provider 边界传递这会对可实现的函数设计施加约束此外为一种新的可 fetch 操作搭建基础设施也比 METHOD 方案更复杂。优点与 3.0 的可扩展性方案一致若在 MVP 中立即实现可避免后续版本采用时返工。缺点实现比简单的 METHOD 方案更复杂复杂对象无法跨 provider 边界传递。选定的方案设计文档明确MVP 采用 METHOD 方案预期后续版本将其转换为完整的、可供第三方应用访问的 provider 方案。也就是说METHOD 方案充当通往 provider 方案的垫脚石stepping stone——MVP 用最小的成本统一四种记录层后续再演进为可 fetch 的算法形态。核心抽象OSSL_RECORD_METHOD 与 OSSL_RECORD_LAYER选定方案的核心是两层抽象OSSL_RECORD_METHOD代表某一种特定类型记录层的实现包含一组函数指针表示该记录层可以执行的各类操作。它是类层面的抽象。OSSL_RECORD_LAYER代表某个特定OSSL_RECORD_METHOD的一次具体实例化保存该 METHOD 在某个连接即某个SSL对象上使用的状态。它是实例层面的抽象。两个关键设计决策读写分离任意SSL对象至少关联 2 个OSSL_RECORD_LAYER对象——一个用于读、一个用于写。某些情况下会多于 2 个例如 DTLS 中可能需要重传之前 epoch 的记录。不同保护级别或不同 epoch 对应不同的OSSL_RECORD_LAYER对象。保护级别可切换 METHOD不同的保护级别可能使用不同的OSSL_RECORD_METHOD。典型场景是连接在握手阶段使用标准 TLS 记录层握手完成后切换到内核 TLSKTLS记录层。这一设计在真实源码 ssl/record/rec_layer_s3.c 的ssl_select_next_record_layer()中得到了完整印证见下文源码实现一节。OSSL_RECORD_LAYER的创建通过所属OSSL_RECORD_METHOD的new函数完成释放通过free函数完成。new函数的参数同时提供记录层所需的全部密码学状态密钥、IV、对称加密算法、哈希算法等。OSSL_RECORD_LAYER的内部结构对 libssl 的其余部分完全隐藏可以针对特定 METHOD 定制。实际工程中标准 TLS、DTLS、KTLS 的OSSL_RECORD_METHOD共用同一个OSSL_RECORD_LAYER结构见 ssl/record/methods/recmethod_local.h 中struct ossl_record_layer_st的定义而 QUIC-TLS 实现预计会采用不同的结构布局。记录写入流程模板、分片与重试写入路径的载体是OSSL_RECORD_TEMPLATE结构——单个记录的头部与负载数据都由它表示。libssl 构造待写记录的模板集合然后交给记录层的write_records函数。大多数情况下一次只写一条记录但在 pipelining 或 multibuffer 优化场景下可以一次写多条。职责划分清晰记录层负责判断自己能否一次处理多条记录libssl负责把负载数据切分成OSSL_RECORD_TEMPLATE对象。具体流程如下libssl 调用记录层的get_max_records()函数确定给定负载应切分为多少条记录若返回值大于 1libssl 构造至多该数量的OSSL_RECORD_TEMPLATE并把整个集合传给记录层的write_records()write_records的实现必须构造适当数量的记录、按需施加保护加密、MAC 等然后写入底层传输 BIO。write_records的返回值语义也写入了 include/internal/recordmethod.h 的OSSL_RECORD_RETURN_*宏返回值宏含义1OSSL_RECORD_RETURN_SUCCESS成功0OSSL_RECORD_RETURN_RETRY重试底层传输要求重试如非阻塞 BIO 返回 would-block-1OSSL_RECORD_RETURN_NON_FATAL_ERR非致命错误可尝试替代记录层-2OSSL_RECORD_RETURN_FATAL致命错误-3OSSL_RECORD_RETURN_EOF到达流末尾关键重试语义实现方必须遵守允许数据部分发送但只要数据没有全部发出仍视为 retry无论成功还是重试libssl 都可以立即释放自己的缓冲区未发送的数据由OSSL_RECORD_LAYER对象自行缓冲直到最终发出发生 retry 后libssl 会反复调用retry_write_records()直到收到成功返回在一次write_records或retry_write_records返回成功之前libssl绝不会第二次调用write_records。记录读取流程缓冲、句柄与释放libssl 通过read_record函数读取记录。OSSL_RECORD_LAYER可能一次读取并缓冲多条记录但read_record每次只返回一条记录。OSSL_RECORD_LAYER对象拥有已读记录的缓冲区并向 libssl 提供指向该缓冲区中负载数据的指针同时返回记录的其他信息长度、数据类型等。每条记录都有一个不透明的句柄rechandle。记录数据必须由OSSL_RECORD_LAYER持续缓冲直到通过release_record()调用释放为止。在当前源码实现中释放语义更细release_record(OSSL_RECORD_LAYER *rl, void *rechandle, size_t length)允许按字节长度分次释放保证按读取顺序释放详见 include/internal/recordmethod.h。在 DTLS 场景下read_record还会通过epoch与seq_num出参回填记录的 epoch 和序列号供防重放 bitmap 校验使用。状态查询接口记录层实现提供一系列函数让 libssl 查询当前状态函数作用unprocessed_read_pending()查询是否已有从底层 BIO 读入、但尚未处理的数据缓冲processed_read_pending()查询是否有已读入并已处理、可被读取的数据缓冲不一定是应用数据app_data_pending()查询已处理的应用数据缓冲量即可立即读取的应用数据字节数get_alert_code()上一次读写记录失败时应使用的 alert 码DTLS/TLS 中即 alert description codeget_state()获取描述记录层当前状态的可打印字符串支持 short/long 两种形式get_compression()获取记录层当前使用的压缩方法信息get_max_record_overhead()获取记录层在传输前会在负载上追加的最大字节数不含压缩引入的扩展目前仅对 DTLS 实现事件通知接口除了查询libssl 还会向记录层通知各种与运行相关的事件函数触发时机与语义set1_bio()记录层底层 BIO 被更换时调用set_protocol_version()协议版本协商选定具体版本时调用set_plain_alerts()指示当前上下文允许接收未加密 alert即使通常期望加密数据仅 TLSv1.3 相关set_first_handshake()任意 (D)TLS 连接的首次握手开始与结束时调用set_max_pipelines()配置记录层一次应处理的管道pipeline最大数量默认 1set_in_init()告知记录层当前是否处于in_init状态创建时默认为 trueset_options()当前选项集合更新时调用set_max_frag_len()设置当前生效的最大分片长度可能来自用户配置或握手协商increment_sequence_ctr()强制记录层递增序列计数器多数情况下记录层自行管理计数器但在DTLSv1_listen()这一特殊场景下libssl 需要以递增后的序列计数器初始化记录层alloc_buffers()/free_buffers()请求记录层分配/释放缓冲区仅是提示hint记录层自行管理缓冲区的分配与释放在 ssl/record/rec_layer_s3.c 的ssl_post_record_layer_select()中可以观察到这些事件的真实触发逻辑首次握手时调用set_first_handshake(thisrl, 1)max_pipelines非零时调用set_max_pipelines()选中 KTLS 时还会强制关闭重协商SSL_OP_NO_RENEGOTIATION。源码实现从设计草案到落地代码附录 A 的 API 与当前实现的演进设计文档附录 A 给出了recordmethod.h的早期草案。对照仓库中实际落地的 include/internal/recordmethod.h可以看到接口已经发生了明显演进OSSL_RECORD_TEMPLATE扩展草案仅有type、version、buf、buflen四个字段当前实现增加了sequence_number与epoch两个uint64_t字段用于 DTLS 场景下对记录序列号与 epoch 的显式携带。new_record_layer签名扩展草案参数key被拆分为secret流量密钥与key加密密钥新增snkeysequence number 加密密钥DTLS 1.3 使用、snciph、kdfdigest、use_urxe、rlarg等参数同时epoch参数由 32 位扩展到uint64_t。新增函数指针set1_peerDTLS 写侧更换对端地址配合BIO_sendmmsg()、set_use_urxe、get_sequence/set_sequence、get_epoch、set_curr_mtu设置 DTLS 当前 MTU、unprocessed_records返回未处理记录队列中的记录数。release_record增加length参数支持按字节分次释放记录缓冲。read_record出参类型调整epoch、seq_num变为uint64_t指针data变为const unsigned char **。内置方法声明文件尾部声明了三个标准内置记录方法——ossl_tls_record_method、ossl_dtls_record_method以及仅在未禁用 KTLSOPENSSL_NO_KTLS时存在的ossl_ktls_record_method。头文件中还保留了两段与设计文档一致的开放问题注释一段质疑是否应改为可 fetch 方案第三方 provider 加载未知OSSL_RECORD_METHOD时selection_cb式的选择方法难以工作一段说明若成为公共 API则需要提供创建/释放OSSL_RECORD_METHOD及读写函数指针的函数——除非改为可 fetch。记录层选择逻辑ssl/record/rec_layer_s3.c 中的ssl_select_next_record_layer()是抽象记录层架构在运行时调度中的直接体现若设置了自定义方法custom_rlmethod优先使用在OSSL_RECORD_PROTECTION_LEVEL_NONE无保护如握手初期时DTLS 选ossl_dtls_record_method否则选ossl_tls_record_method在OSSL_RECORD_PROTECTION_LEVEL_APPLICATION且启用了SSL_OP_ENABLE_KTLS、且连接为 TLSv1.3 或首次握手时选择ossl_ktls_record_methodKTLS 不支持重协商是重要约束否则默认沿用当前读写方向的 METHOD。这正对应设计文档中连接可能从标准 TLS 记录层开始握手握手完成后切换到内核 TLS 记录层的描述。协议版本特定的函数表在 ssl/record/methods/recmethod_local.h 中struct record_functions_st是一层更细粒度的协议版本特定函数指针表包含set_crypto_state、cipher、mac、read_n、get_more_records、validate_record_header、post_process_record、get_max_records、write_records、allocate_write_buffers、initialise_write_packets、get_record_type、prepare_record_header、add_record_padding、prepare_for_encryption、post_encryption_processing、prepare_write_bio等。文件末尾导出了六个内置函数表tls_1_funcs、tls_1_3_funcs、tls_any_funcs、dtls_1_funcs、dtls_1_3_funcs、dtls_any_funcs。以 ssl/record/methods/tls1_meth.c 为例tls_1_funcsTLSv1.0/1.1/1.2 共用与dtls_1_funcs对比可以清晰看到差异点DTLS 不处理 empty fragmentBEAST 缓解措施仅 TLSv1.0 需要、不使用 multiblock 写路径、prepare_record_header换成 DTLS 专用版本、加密后处理换成dtls_post_encryption_processing。tls1_set_crypto_state中对 GCM/CCM/CBC 模式分别初始化加密上下文、并计算显式 IV 长度的逻辑以及tls1_cipher中 AEAD 记录头构造setup_record_header与 pipeline 加解密EVP_CTRL_SET_PIPELINE_OUTPUT_BUFS等控制字的实现都是理解记录层加密细节的第一手材料。各记录层实现的文件分布如下TLS 1.0-1.2tls1_meth.c含 pipeline 加密、tls_multib.cmultiblock 写路径、tls_pad.c填充/MACTLS 1.3tls13_meth.cTLS any版本协商期tlsany_meth.cDTLSdtls_meth.c含 DTLS 1.3 统一头部与加密序列号处理KTLSktls_meth.c公共逻辑tls_common.c缓冲管理、重试写、状态查询等通用实现QUIC 中的运用QUIC-TLS 记录层QUIC 复用了 TLS 握手来建立密钥但不使用标准 TLS 记录层——QUIC 自行负责 QUIC 报文packet的机密性与完整性只有 TLS 握手被使用应用数据完全由 QUIC 保护。因此在 QUIC 语境下新记录层的记录形态就是QUIC CRYPTO frames即握手消息的载体。QUIC-TLS 集成细节可参考 quic-tls.md一个QUIC_TLS对象管理 QUIC-TLS 握手核心接口为ossl_quic_tls_new创建接收QUIC_TLS_ARGS参数结构其中包含 innerSSL对象与一组回调、ossl_quic_tls_free释放、ossl_quic_tls_tick推进握手状态消费/产出 CRYPTO 帧数据并触发回调。QUIC_TLS_ARGS中的crypto_send_cb/crypto_recv_cb负责 CRYPTO 流数据的收发yield_secret_cb在每个 TLS 保护级别产生流量密钥时被调用——这些密钥随后会交给 QUIC 自身的报文保护层而非标准记录层。这解释了设计文档开篇的定位QUIC record layer is handled separately——抽象记录层接口的价值在于统一并隔离TLS/DTLS/KTLS 的实现差异同时为 QUIC-TLS 预留一致的接入形态而不是让 QUIC 复用现有的记录格式。未来展望从 METHOD 到可 fetch 的 provider 方案设计文档明确了演进路径MVP 用 METHOD 方案交付内部可插拔接口与单流 QUIC 客户端s_client功能完备后接口将转为公共 API并通过 provider 以可 fetch 的方式提供稳定 ABI。届时需要解决的头文件中留存的开放问题包括可 fetch 后如何用字符串替代EVP_CIPHER/NID 形式的mactype参数、如何在加载未知第三方 provider 的场景下可靠选择匹配的记录层实现协议、密码套件、扩展都参与匹配等。对于希望深入研读该架构的读者建议按以下顺序阅读仓库源码先读 include/internal/recordmethod.h 建立接口全貌再读 ssl/record/methods/recmethod_local.h 理解OSSL_RECORD_LAYER内部状态与版本函数表随后以 ssl/record/methods/tls_common.c 为公共实现主干对照 ssl/record/methods/tls1_meth.c、ssl/record/methods/tls13_meth.c、ssl/record/methods/dtls_meth.c 与 ssl/record/methods/ktls_meth.c 观察各变体的差异实现最后回到 ssl/record/rec_layer_s3.c 的ssl_select_next_record_layer()与ssl_set_new_record_layer()看运行时调度与创建流程即可完整掌握这一抽象记录层架构的设计与落地全貌。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表