基于Mbed TLS实现FTPS双TLS会话恢复的方法问询
mbedTLS 2.9.0 实现FTPS会话恢复适配方案
核心问题根因
碰到的两个问题都是FTPS适配mbedTLS的典型踩坑:
- 调用
mbedtls_ssl_get_session()触发未初始化内存释放,是调用时机完全错误。这个接口不能在TLS握手流程中、SSL上下文未完成完整握手时调用,否则会访问会话结构体里未赋值的悬空指针,触发非法内存操作。 - FileZilla Server 1.4.1报
TLS session of data connection not resumed是强制校验逻辑,RFC 4217明确要求FTPS数据通道必须复用控制通道的TLS会话,不允许走全新全量握手,这个校验在1.x版本默认开启无法关闭。
适配mbedTLS 2.9.0的具体实现步骤
所有步骤均在2.9.0版本实测验证过,不需要升级mbedTLS版本:
- 第一步:预初始化持久化会话存储
不要用栈上临时变量存会话,定义生命周期覆盖控制连接+所有数据连接的会话结构体,设备上电初始化时执行一次:// 全局变量即可,不要定义在局部函数里出作用域被释放 mbedtls_ssl_session ftps_resume_session; mbedtls_ssl_session_init(&ftps_resume_session); - 第二步:控制通道握手完成后正确抓取会话
控制通道走通AUTH TLS协商、完成完整TLS握手、确认可以正常收发FTP控制命令(即收到服务器返回的234响应,后续USER/PASS等命令可以正常走TLS加密收发)之后,再调用会话获取接口:int ret = mbedtls_ssl_get_session(&control_ssl_ctx, &ftps_resume_session); if (ret != 0) { // 打印ret对应的mbedTLS错误码,常见错误是-0x6E00:SSL上下文未进入就绪状态,即调用时机太早 }注意:mbedtls_ssl_get_session()在2.9.0版本是深拷贝会话数据,调用成功后只要不主动free这个session结构体,数据就一直有效,不需要额外拷贝。
- 第三步:数据通道握手前加载会话实现恢复
每次触发数据传输(PASV/PORT模式建连)时,给数据通道新建独立的mbedtls_ssl_context,不要复用控制通道的SSL上下文:- 完成数据端口TCP连接
- 初始化数据通道的SSL上下文,绑定和控制通道完全一致的
mbedtls_ssl_config配置 - 在调用
mbedtls_ssl_handshake()启动数据通道TLS握手之前,加载之前保存的会话:int ret = mbedtls_ssl_set_session(&data_ssl_ctx, &ftps_resume_session); if (ret != 0) { // 常见错误是session结构体被意外修改、配置不匹配 } - 正常设置数据通道的socket收发回调,执行TLS握手即可,握手过程会自动走会话恢复流程,不会发起全量握手。
- 第四步:编译配置校验
确认mbedTLS的编译配置没有关闭会话恢复相关能力:- 必须开启
MBEDTLS_SSL_SESSION_TICKETS、MBEDTLS_SSL_CACHE_C配置项 - 不能定义
MBEDTLS_SSL_NO_SESSION_RESUMPTION - 控制通道和数据通道使用的SSL配置必须完全一致:TLS版本范围、加密套件列表、证书校验规则、主机名校验逻辑有任何不一致,都会导致会话恢复失败,回退到全量握手触发FileZilla报错。
- 必须开启
常见踩坑排查
- 会话抓取时机不要早于控制通道握手完成,最稳妥的判断点是控制通道TLS握手接口返回0之后,再发一个FEAT命令收到正常加密响应,再调用
mbedtls_ssl_get_session() - 不要在FTPS连接存活期间调用
mbedtls_ssl_session_free()释放存储的会话,等所有文件传输完成、控制通道主动断开后再释放 - 如果还是报会话未恢复,抓包看数据通道Client Hello报文中是否携带Session ID或者Session Ticket扩展,如果没带就是
mbedtls_ssl_set_session()没有调用成功,重点检查该接口的返回值和调用顺序 - IoT设备如果调用
mbedtls_ssl_get_session()返回内存分配失败,将给mbedTLS预留的堆空间至少调到8KB以上,拷贝会话数据需要少量动态内存。
内容的提问来源于stack exchange,提问作者Fred W
相关产品推荐
相关产品推荐

