拓冰建站拓冰建站
首页 / 资讯中心 / 正文

mbedTLS TLS 1.3 Early Data(0-RTT)开发指南:发送与接收的完整 API 用法与源码解析

mbedTLS TLS 1.3 Early Data0-RTT开发指南发送与接收的完整 API 用法与源码解析【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware本文基于 flipperzero-firmware 仓库内嵌的 Mbed TLS 源码位于 lib/mbedtls以 lib/mbedtls/docs/tls13-early-data.md 为骨架深入讲解 TLS 1.3 Early Data又称 0-RTT 数据在 Mbed TLS 中的编程模型。读完本文你将掌握mbedtls_ssl_write_early_data()、mbedtls_ssl_read_early_data()、mbedtls_ssl_get_early_data_status()三组 API 的完整调用流程理解服务端拒绝 Early Data 时的重发策略并能看懂配套测试用例与示例程序可直接在自己的嵌入式 TLS 应用中落地 0-RTT 优化。背景什么是 TLS 1.3 Early DataTLS 1.3RFC 8446引入了一个显著降低连接建立延迟的机制Early Data0-RTT 数据。当客户端持有一张允许 Early Data 的 PSK 会话票据Session Ticket时可以在握手的首轮飞行first flight中、在握手完成之前就把应用数据随 ClientHello 一起发送出去。这样客户端可以省去一个完整的往返时延RTT因此也被称为 0-RTT 数据。需要特别强调的是0-RTT 数据的安全属性弱于普通 TLS 数据。在 lib/mbedtls/include/mbedtls/ssl.h 的 API 文档中Mbed TLS 明确引用了 RFC 8446 第 2.3 节的警告0-RTT 数据不具备前向保密性因为它仅由所协商的 PSK 派生的密钥加密0-RTT 数据在连接之间不保证防重放non-replay。普通 TLS 1.3 1-RTT 数据依靠服务端 Random 值防重放而 0-RTT 数据不依赖 ServerHello因此防护更弱Mbed TLS没有实现RFC 8446 第 8 节定义的任何防重放机制票据的一次性使用、时间窗口内的 ClientHello 记录。因此在设计应用时应只把幂等、可安全重放的数据例如 HTTP 请求、缓存刷新指令放进 Early Data。启用编译开关与默认配置在 Mbed TLS 中Early Data 功能默认是关闭的需要编译期宏和运行时配置两步开启。编译期宏MBEDTLS_SSL_EARLY_DATA在 lib/mbedtls/include/mbedtls/mbedtls_config.h 中该宏的注释说明如下/** * \def MBEDTLS_SSL_EARLY_DATA * * Enable support for RFC 8446 TLS 1.3 early data. * * Requires: MBEDTLS_SSL_SESSION_TICKETS and either * MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED or * MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED * * Comment this to disable support for early data. If MBEDTLS_SSL_PROTO_TLS1_3 * is not enabled, this option does not have any effect on the build. */ //#define MBEDTLS_SSL_EARLY_DATA可见开启 Early Data 有明确的依赖链依赖项说明MBEDTLS_SSL_PROTO_TLS1_3必须启用 TLS 1.3 协议本身否则该宏不产生任何效果MBEDTLS_SSL_SESSION_TICKETSEarly Data 依托会话票据传递能力MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED或MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLEDEarly Data 只能基于 PSK 密钥交换模式协商取消该行的//注释即可启用。需要说明的是本文撰写时仓库默认配置中该宏处于注释状态即默认构建不含 Early Data 支持实际使用前请根据你的 mbedtls 配置确认宏状态。单次票据可携带的最大 Early Data 量lib/mbedtls/include/mbedtls/mbedtls_config.h 还定义了默认上限#define MBEDTLS_SSL_MAX_EARLY_DATA_SIZE 1024这是服务端在NewSessionTicket消息中通过 early data 扩展通告给客户端的默认最大值单位字节。可在运行时通过mbedtls_ssl_conf_max_early_data_size()覆盖见下文。运行时开关mbedtls_ssl_conf_early_data()服务端和客户端都必须在运行时显式启用 Early Data。对应函数在 lib/mbedtls/include/mbedtls/ssl.h 中声明void mbedtls_ssl_conf_early_data(mbedtls_ssl_config *conf, int early_data_enabled);取值由 lib/mbedtls/include/mbedtls/ssl.h 定义#define MBEDTLS_SSL_EARLY_DATA_DISABLED 0 #define MBEDTLS_SSL_EARLY_DATA_ENABLED 1默认值client 和 server 两侧都是MBEDTLS_SSL_EARLY_DATA_DISABLED。启用后Early Data 才会在握手中被协商。服务端还可调用mbedtls_ssl_conf_max_early_data_size()调整单张票据允许的 0-RTT 数据上限默认即MBEDTLS_SSL_MAX_EARLY_DATA_SIZE即 1024 字节声明见 lib/mbedtls/include/mbedtls/ssl.hvoid mbedtls_ssl_conf_max_early_data_size( mbedtls_ssl_config *conf, uint32_t max_early_data_size);该值写入服务端发送的NewSessionTicket消息的 early data 指示扩展中。需要注意该接口不会影响已签发、客户端可能已缓存的旧票据所能携带的 Early Data 量详见 lib/mbedtls/include/mbedtls/ssl.h 的 warning 注释另外如果服务端接收到的 Early Data 超过票据允许值将直接终止连接因此该值应足够大以容纳最少量的 Early Data 交换lib/mbedtls/include/mbedtls/ssl.h。客户端写入 Early Data三个核心 API客户端侧的核心 API 有三个均在 lib/mbedtls/include/mbedtls/ssl.h 中声明受#if defined(MBEDTLS_SSL_EARLY_DATA) defined(MBEDTLS_SSL_CLI_C)保护第 5318-5433 行API作用mbedtls_ssl_write_early_data()在握手进行中尝试写出 Early Data第 5403 行mbedtls_ssl_get_early_data_status()握手完成后查询服务端是否接受了 Early Data第 5432 行mbedtls_ssl_read_early_data()服务端专用读取收到的 Early Data第 5314 行受MBEDTLS_SSL_SRV_C保护mbedtls_ssl_write_early_data()的签名int mbedtls_ssl_write_early_data(mbedtls_ssl_context *ssl, const unsigned char *buf, size_t len);它与mbedtls_ssl_write()行为相似但有两点关键差异见 lib/mbedtls/include/mbedtls/ssl.h 的注释mbedtls_ssl_write()的目标是先完成握手再写应用数据mbedtls_ssl_write_early_data()的目标是把握手推进到无法再发送 Early Data 为止——即尽可能早、尽可能多地发出 0-RTT 数据。此外该 API 的返回值相比mbedtls_ssl_write()多了一个特有的错误码MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA-0x7C80定义于 lib/mbedtls/include/mbedtls/ssl.h。文档指出lib/mbedtls/include/mbedtls/ssl.h出现该错误码意味着曾经可以、但现在不行客户端已收到服务端 Finished 消息、服务端拒绝了 Early Data、或当前 PSK 允许的 Early Data 量已达上限从来就不行、以后也不行该上下文禁用了 Early Data、上下文未正确配置、或首次调用时握手已经完成出现该错误后对该 API 的任何后续调用都会返回同一错误码但不影响继续使用mbedtls_ssl_write()、mbedtls_ssl_read()或mbedtls_ssl_handshake()完成握手。还有两个值得注意的约定lib/mbedtls/include/mbedtls/ssl.h只有为握手配置了允许 Early Data 的 PSK 时该函数才可能真正写出 Early Data该函数不提供服务端是否接受 Early Data 的任何信息返回正值只代表数据已写出。要确认服务端是否接受必须在握手完成后调用mbedtls_ssl_get_early_data_status()。客户端代码模式一只发送 Early Data原文档给出的第一个完整代码模式是只写 Early Data。先看对照用的普通写入函数int write_data(mbedtls_ssl_context *ssl, const unsigned char *data_to_write, size_t data_to_write_len, size_t *data_written) { int ret; *data_written 0; while (*data_written data_to_write_len) { ret mbedtls_ssl_write(ssl, data_to_write *data_written, data_to_write_len - *data_written); if (ret 0 ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { return ret; } *data_written ret; } return 0; }其中ssl是要使用的 SSL 上下文data_to_write是数据缓冲地址data_to_write_len是数据字节数。函数被调用时该上下文的握手可能尚未完成、甚至尚未开始此时mbedtls_ssl_write()会自动透明地完成握手后再把数据写发给服务端。由于一次调用可能无法写完全部数据需要用循环持续调用直到写完。只发送 Early Data 的函数与上面几乎完全一致仅把mbedtls_ssl_write()换成mbedtls_ssl_write_early_data()int write_early_data(mbedtls_ssl_context *ssl, const unsigned char *data_to_write, size_t data_to_write_len, size_t *data_written) { int ret; *data_written 0; while (*data_written data_to_write_len) { ret mbedtls_ssl_write_early_data(ssl, data_to_write *data_written, data_to_write_len - *data_written); if (ret 0 ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { return ret; } *data_written ret; } return 0; }注意两点与write_data()相比write_early_data()还可能返回MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA调用者必须专门处理该错误不能像对待 WANT_READ/WANT_WRITE 那样静默忽略调用write_early_data()时SSL 上下文应处于全新状态——典型场景是刚执行完mbedtls_ssl_setup()或mbedtls_ssl_session_reset()之后确保还有写 Early Data 的窗口期。客户端代码模式二尽可能 Early Data其余走标准握手后数据实战中更常见的是能发多少 Early Data 就发多少剩余数据等握手完成后以普通应用数据发送。原文档给出了组合代码ret write_early_data(ssl, data_to_write, data_to_write_len, early_data_written); if (ret 0 ret ! MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA) { goto error; } ret write_data(ssl, data_to_write early_data_written, data_to_write_len - early_data_written, data_written); if (ret 0) { goto error; } data_written early_data_written;逻辑拆解先调用write_early_data()尽力发送 Early Dataearly_data_written记录实际写出的字节数唯一允许非致命的错误是MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA表示窗口已关闭或不可用此时early_data_written应视为 0程序应继续走普通发送路径而不是直接goto error剩余部分data_to_write early_data_written交给write_data()后者内部的mbedtls_ssl_write()会自动完成握手后再发送最后把两段字节数累加得到总发送量。这段代码的完整性依赖前文write_early_data()的设计当它返回MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA时early_data_written仍保持 0函数初始化*data_written 0从而自然退化为纯普通发送路径。客户端代码模式三处理服务端拒绝 Early DataEarly Data 可能被服务端拒绝例如票据过期、ALPN 不匹配、重放窗口等原因。此时客户端必须知道哪些数据白发了并把它们作为普通握手后数据重发。原文档给出的完整代码ret write_early_data(ssl, data_to_write, data_to_write_len, early_data_written); if (ret 0 ret ! MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA) { goto error; } /* * Make sure the handshake is completed as it is a requisite of * mbedtls_ssl_get_early_data_status(). */ while (!mbedtls_ssl_is_handshake_over(ssl)) { ret mbedtls_ssl_handshake(ssl); if (ret 0 ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { goto error; } } ret mbedtls_ssl_get_early_data_status(ssl); if (ret 0) { goto error; } if (ret MBEDTLS_SSL_EARLY_DATA_STATUS_REJECTED) { early_data_written 0; } ret write_data(ssl, data_to_write early_data_written, data_to_write_len - early_data_written, data_written); if (ret 0) { goto error; } data_written early_data_written;关键点在于mbedtls_ssl_get_early_data_status()。它的返回值在 lib/mbedtls/include/mbedtls/ssl.h 中定义typedef enum { MBEDTLS_SSL_EARLY_DATA_STATUS_NOT_INDICATED, /* 客户端未向服务端指示使用 Early Data */ MBEDTLS_SSL_EARLY_DATA_STATUS_ACCEPTED, /* 服务端接受了 Early Data */ MBEDTLS_SSL_EARLY_DATA_STATUS_REJECTED, /* 服务端拒绝了 Early Data */ } mbedtls_ssl_early_data_status;调用前提与约束lib/mbedtls/include/mbedtls/ssl.h该 API 是客户端专用从服务端调用会返回MBEDTLS_ERR_SSL_BAD_INPUT_DATA必须在握手完成后调用否则同样返回MBEDTLS_ERR_SSL_BAD_INPUT_DATA——这正是上面代码先用while (!mbedtls_ssl_is_handshake_over(ssl))循环确保握手结束的原因若状态为REJECTED客户端应把之前经mbedtls_ssl_write_early_data()发送的数据视为未送达将early_data_written归零并通过mbedtls_ssl_write()作为普通握手后应用数据重新发送。这也呼应了 0-RTT 的本质约束被拒绝后重发是预期内行为所以 Early Data 携带的内容必须能被安全地重复发送。服务端读取 Early Data握手循环中的特殊错误码服务端侧Mbed TLS 提供mbedtls_ssl_read_early_data()来读取 TLS 1.3 握手期间可能收到的 Early Data声明见 lib/mbedtls/include/mbedtls/ssl.h受#if defined(MBEDTLS_SSL_EARLY_DATA) defined(MBEDTLS_SSL_SRV_C)保护int mbedtls_ssl_read_early_data(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len);它只应在服务端使用。若上下文不适合读取 Early Data例如未经历返回MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA的调用返回MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA-0x7B80定义于 lib/mbedtls/include/mbedtls/ssl.h输入无效则返回MBEDTLS_ERR_SSL_BAD_INPUT_DATA成功则返回实际读取的正字节数。通过特殊错误码感知 Early Data 到达在使用mbedtls_ssl_handshake()、mbedtls_ssl_read()、mbedtls_ssl_write()组合建立 TLS 1.3 连接的过程中当服务端启用了 Early Data仅此情形下这些触发/恢复握手序列的 API 可能返回特殊错误码#define MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA -0x7C00该错误码定义于 lib/mbedtls/include/mbedtls/ssl.h语义是已收到一些 Early Data请调用mbedtls_ssl_read_early_data()读取详见 lib/mbedtls/include/mbedtls/ssl.h 的mbedtls_ssl_conf_early_data()文档。典型的原始握手循环while ((int ret mbedtls_ssl_handshake(ssl)) ! 0) { if (ret 0 ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { break; } }可以改造成支持 Early Data 读取的版本size_t data_read_len 0; while ((ret mbedtls_ssl_handshake(ssl)) ! 0) { if (ret MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA) { ret mbedtls_ssl_read_early_data(ssl, buffer data_read_len, sizeof(buffer) - data_read_len); if (ret 0) { break; } data_read_len ret; continue; } if (ret 0 ret ! MBEDTLS_ERR_SSL_WANT_READ ret ! MBEDTLS_ERR_SSL_WANT_WRITE) { break; } }要点当mbedtls_ssl_handshake()返回MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA时立即调用mbedtls_ssl_read_early_data()把数据读入buffer累加data_read_len然后continue继续驱动握手每次 Early Data 到达都走这个分支可能多次直到握手最终返回 0 或以真正的错误退出mbedtls_ssl_read_early_data()的调用时机有严格约定必须是紧跟在某个返回了MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA的mbedtls_ssl_handshake()/mbedtls_ssl_handshake_step()/mbedtls_ssl_read()/mbedtls_ssl_write()调用之后见 lib/mbedtls/include/mbedtls/ssl.h。服务端丢弃 Early Data 的三种策略lib/mbedtls/include/mbedtls/ssl.h 还定义了服务端在不想处理 Early Data 时的丢弃策略#define MBEDTLS_SSL_EARLY_DATA_NO_DISCARD 0 #define MBEDTLS_SSL_EARLY_DATA_TRY_TO_DEPROTECT_AND_DISCARD 1 #define MBEDTLS_SSL_EARLY_DATA_DISCARD 2对应的discard_early_data_record字段记录在 SSL 上下文结构体中lib/mbedtls/include/mbedtls/ssl.h可结合服务端业务策略选择直接丢弃尝试解密后丢弃或不丢弃。仓库中的配套实现与验证证据示例程序ssl_client2仓库内的演示客户端 lib/mbedtls/programs/ssl/ssl_client2.c 提供了可直接运行的 Early Data 参考实现命令行参数early_dataN其中N取值 0禁用MBEDTLS_SSL_EARLY_DATA_DISABLED或 1启用MBEDTLS_SSL_EARLY_DATA_ENABLED默认走库默认值lib/mbedtls/programs/ssl/ssl_client2.c参数解析后调用mbedtls_ssl_conf_early_data(conf, opt.early_data)配置lib/mbedtls/programs/ssl/ssl_client2.c实际发送段lib/mbedtls/programs/ssl/ssl_client2.c展示了与文档一致的循环do { while ((ret mbedtls_ssl_write_early_data(ssl, buf written, len - written)) 0) { if (ret MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA) goto end_of_early_data; ... } written ret; } while (written len);随后才进入mbedtls_ssl_handshake()完成余下握手并打印实际写入的 Early Data 字节数与分片数。自动化测试test_suite_sslEarly Data 的端到端行为在 lib/mbedtls/tests/suites/test_suite_ssl.function 中有系统性的测试覆盖可直接作为正确用法的行为准则参考tls13_write_early_data(int scenario)第 4287 行起覆盖多组场景验证客户端调用mbedtls_ssl_write_early_data()的返回值要么等于实际写入的early_data_len要么返回MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA例如窗口关闭、超量、被拒等同时服务端在握手循环中通过MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATAmbedtls_ssl_read_early_data()完成对称读取第 4558-4564 行tls13_read_early_data(int scenario)第 3712 行起验证服务端读取路径超限测试第 4647-4763 行持续写入直到服务端收到的 Early Data 达到max_early_data_size上限确认客户端随后返回MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA且服务端逐字节校验数据内容的一致性辅助函数write_early_data()第 38-63 行在不应使用公共 API的负向测试场景中直接通过内部字段ssl-out_msg/mbedtls_ssl_write_record()手工构造 Early Data 记录用于验证mbedtls_ssl_write_early_data()对不可写上下文的正确拒绝行为。常见问题与易错点忘记启用编译宏MBEDTLS_SSL_EARLY_DATA未定义时上述所有 API 均不存在编译即报错。启用前请同时确认MBEDTLS_SSL_PROTO_TLS1_3、MBEDTLS_SSL_SESSION_TICKETS与 PSK 密钥交换模式宏均已开启。忘记运行时启用即使编译宏打开默认MBEDTLS_SSL_EARLY_DATA_DISABLED也不会协商 Early Data客户端调用mbedtls_ssl_write_early_data()将直接返回MBEDTLS_ERR_SSL_CANNOT_WRITE_EARLY_DATA。把 CANNOT_WRITE_EARLY_DATA 当致命错误它只是Early Data 窗口关闭的信号正确做法是回退到普通mbedtls_ssl_write()继续发送而不是终止连接。在握手完成前查询状态mbedtls_ssl_get_early_data_status()要求握手已完成否则返回MBEDTLS_ERR_SSL_BAD_INPUT_DATA文档示例中用mbedtls_ssl_is_handshake_over()循环显式完成握手后再查询。服务端读错 APImbedtls_ssl_read_early_data()是服务端专用mbedtls_ssl_get_early_data_status()是客户端专用二者不可互换且mbedtls_ssl_read_early_data()只能紧跟在返回MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA的握手/读写调用之后调用。忽略 0-RTT 的较弱安全属性Early Data 无前向保密、不保证防重放且 Mbed TLS 未实现 RFC 8446 第 8 节的防重放机制务必只发送幂等数据。小结TLS 1.3 Early Data 是减少首包延迟的重要手段Mbed TLS 通过三组 APImbedtls_ssl_write_early_data()、mbedtls_ssl_read_early_data()、mbedtls_ssl_get_early_data_status()提供了完整支持客户端以循环尽力发送 0-RTT 数据、以CANNOT_WRITE_EARLY_DATA作为窗口关闭信号回退普通发送并在握手完成后依据EARLY_DATA_STATUS_REJECTED重发被拒数据服务端则在握手循环中识别MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA并读取。仓库中的 ssl_client2.c 与 test_suite_ssl.function 提供了可直接对照的可运行实现与行为契约是落地 0-RTT 时最值得参考的第一手资料。更完整的 API 语义说明可继续阅读 lib/mbedtls/docs/tls13-early-data.md 与 lib/mbedtls/include/mbedtls/ssl.h。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门