news 2026/9/11 8:18:19

libcurl CURLOPT_SSH_KEYDATA 详解:向 SSH 主机密钥回调传递自定义数据的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libcurl CURLOPT_SSH_KEYDATA 详解:向 SSH 主机密钥回调传递自定义数据的完整指南

libcurl CURLOPT_SSH_KEYDATA 详解:向 SSH 主机密钥回调传递自定义数据的完整指南

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

导读

CURLOPT_SSH_KEYDATA是 libcurl 中用于 SFTP/SCP 连接安全校验的一对配套选项之一:它本身不触发任何校验逻辑,而是为CURLOPT_SSH_KEYFUNCTION设置的回调提供一个**原样透传(verbatim)**的用户自定义指针,让应用可以把任意上下文(如配置结构体、日志句柄、密钥缓存等)带入到主机密钥匹配回调中。读完本文,你将掌握该选项的签名、在 libcurl 源码中的存储与传递路径、与回调及其返回值的关系,以及一套可直接编译运行的最小示例。

选项定位:它是"数据通道"而非"校验开关"

在 libcurl 的 SSH 校验体系里,CURLOPT_SSH_KEYDATA(官方文档)承担的角色非常单一且明确:

Pass a void * as parameter. Thispointeris passed along verbatim to the callback set with CURLOPT_SSH_KEYFUNCTION(3).

即:你传进去的void *指针,会被 libcurl 原封不动地交给通过CURLOPT_SSH_KEYFUNCTION注册的回调函数,作为其最后一个参数clientp出现。它不影响校验结果本身,校验决策完全由回调函数根据收到的四个参数做出。它只在 SFTP 和 SCP 两个协议下生效(该选项在文档中声明支持Protocol: SFTP, SCP),从 7.19.6 版本开始提供。

函数签名

#include <curl/curl.h> CURLcode curl_easy_setopt(CURL *handle, CURLOPT_SSH_KEYDATA, void *pointer);
  • handle:通过curl_easy_init()获得的 easy handle。
  • pointer:任意用户指针,可以为NULL
  • 返回值:CURLcodeCURLE_OK (0)表示设置成功,非零值表示出错(参见 libcurl-errors)。

在 libcurl 的选项注册表 lib/easyoptions.c 中,该选项的类型被登记为CURLOT_CBPTR(callback pointer),说明它属于"伴随回调使用的指针"一类,与SSH_KEYFUNCTIONCURLOT_FUNCTION)成对出现。

源码视角:从 setopt 到回调的完整传递链

1. 存储:setopt阶段

在 lib/setopt.c 中可以看到该选项的唯一处理逻辑:

case CURLOPT_SSH_KEYDATA: /* * Custom client data to pass to the SSH keyfunc callback */ s->ssh_keyfunc_userp = ptr; break;

它只是把指针存入 easy handle 的set结构体成员ssh_keyfunc_userp,不做任何拷贝、校验或转换——这正符合"原样透传"的语义。

2. 承载:easy handle 数据结构

在 lib/urldata.h 中,该成员与回调函数指针紧邻存放:

#ifdef USE_SSH curl_sshkeycallback ssh_keyfunc; /* key matching callback */ void *ssh_keyfunc_userp; /* custom pointer to callback */ uint32_t ssh_auth_types; /* allowed SSH auth types */ ... #endif

注意它被#ifdef USE_SSH包裹:只有在编译 libcurl 时启用了 SSH 后端(libssh2 或 libssh)的情况下,该选项才实际生效

3. 回调签名与参数语义

回调类型在 include/curl/curl.h 中定义:

enum curl_khtype { CURLKHTYPE_UNKNOWN, CURLKHTYPE_RSA1, CURLKHTYPE_RSA, CURLKHTYPE_DSS, CURLKHTYPE_ECDSA, CURLKHTYPE_ED25519 }; struct curl_khkey { const char *key; /* base64 编码字符串;若 len 非零则为 "raw" 原始数据 */ size_t len; enum curl_khtype keytype; }; typedef int (*curl_sshkeycallback)(CURL *easy, const struct curl_khkey *knownkey, /* known_hosts 中的密钥 */ const struct curl_khkey *foundkey, /* 远端主机下发的密钥 */ enum curl_khmatch, /* libcurl 对匹配状态的判断 */ void *clientp); /* 由 CURLOPT_SSH_KEYDATA 传入 */

回调的最后一个参数clientp,正是CURLOPT_SSH_KEYDATA存入的ssh_keyfunc_userp。当未设置该选项时,clientpNULL——所以回调内部要做空指针防御。

4. 实际调用点:两个 SSH 后端

libcurl 支持两种 SSH 实现后端,二者在调用回调时都把data->set.ssh_keyfunc_userp作为最后一个实参传入:

  • libssh2 后端:lib/vssh/libssh2.c
  • libssh 后端:lib/vssh/libssh.c

两处代码都以类似形式调用:

rc = func(data, knownkeyp, /* from the knownhosts file */ &foundkey, /* from the remote host */ keymatch,>struct mine { void *custom; }; static int keycb(CURL *easy, const struct curl_khkey *knownkey, const struct curl_khkey *foundkey, enum curl_khmatch match, void *clientp) { /* 'clientp' points to the callback_data struct */ /* investigate the situation and return the correct value */ return CURLKHSTAT_FINE_ADD_TO_FILE; } int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; struct mine callback_data; curl_easy_setopt(curl, CURLOPT_URL, "sftp://example.com/thisfile.txt"); curl_easy_setopt(curl, CURLOPT_SSH_KEYFUNCTION, keycb); curl_easy_setopt(curl, CURLOPT_SSH_KEYDATA, &callback_data); curl_easy_setopt(curl, CURLOPT_SSH_KNOWNHOSTS, "/home/user/known_hosts"); result = curl_easy_perform(curl); curl_easy_cleanup(curl); } }

示例要点:

  1. callback_data是栈上分配的结构体,通过&callback_data传入CURLOPT_SSH_KEYDATA
  2. 回调中通过clientp拿回该结构体指针,即可访问应用自定义数据;
  3. CURLOPT_SSH_KNOWNHOSTS必须一并设置,否则回调不会被触发(详见 CURLOPT_SSH_KEYFUNCTION 中 "The callback is only called if CURLOPT_SSH_KNOWNHOSTS(3) is also set" 的说明);
  4. 回调返回CURLKHSTAT_FINE_ADD_TO_FILE,表示信任该主机并把密钥写入 known_hosts(典型的 "trust on first use" 场景)。

与相关选项的组合使用

从 lib/vssh/libssh.c 的注释可以看到,libcurl 对 SSH 主机密钥校验提供了一条优先级链:

  1. 设置了CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256:按 SHA256 哈希校验;
  2. 设置了CURLOPT_SSH_HOST_PUBLIC_KEY_MD5:按 MD5 哈希校验;
  3. 设置了CURLOPT_SSH_KEYFUNCTION回调:做 trust-on-first-use,若回调返回CURLKHSTAT_FINE_ADD_TO_FILE还会写入 known_hosts;
  4. 以上均未设置:仅当主机已存在于 known_hosts 时才接受。

CURLOPT_SSH_KEYDATA属于第 3 条路径的辅助数据通道。配合CURLOPT_SSH_KNOWNHOSTS(known_hosts 文件路径)一起使用,可以让应用在回调中同时拿到"known_hosts 中的已知密钥"、"远端下发的实际密钥"、"libcurl 的匹配判断(CURLKHMATCH_OK/MISMATCH/MISSING)"以及"自定义上下文"四个维度的信息,从而自行决定放行、拒绝或写入。

测试用例佐证

仓库测试 tests/data/test1459 提供了 "SFTP with corrupted known_hosts" 的真实验证场景:它通过 curl 命令行--knownhosts %LOGDIR/known%TESTNUMBER指向一个内容被篡改的 known_hosts 文件(该命令行选项对应的正是CURLOPT_SSH_KNOWNHOSTS),并期望连接以错误码60CURLE_PEER_FAILED_VERIFICATION)失败。这从侧面印证了 known_hosts 校验失败时的行为路径:即使不经过用户回调,libcurl 也会在密钥不匹配时拒绝继续连接。而一旦你设置了CURLOPT_SSH_KEYFUNCTION+CURLOPT_SSH_KEYDATA,这个"拒绝"的决策权就交还给了你的回调。

默认值与返回值

  • 默认值NULL。未设置时,回调收到的clientpNULL,回调中需自行处理。
  • curl_easy_setopt返回值CURLE_OK (0)表示设置成功,非零表示错误,具体错误码参见 libcurl-errors。

实践建议

  1. 生命周期管理CURLOPT_SSH_KEYDATA只保存指针,不拷贝内容。传入的数据必须保证在curl_easy_perform返回之前一直有效(栈上结构体、静态变量、堆上对象均可,但不要在回调返回后继续使用被释放的指针)。
  2. 空指针防御:回调第一个参数easyknownkey(在主机不在 known_hosts 中时为NULL)以及clientp(未设置 KEYDATA 时)都可能为NULL,引用前务必判空。
  3. 多线程注意:该选项是 easy handle 级配置,不同 easy handle 之间互不影响;但同一 handle 不应在多线程中并发执行curl_easy_perform
  4. 文件写入权限:若回调返回CURLKHSTAT_FINE_ADD_TO_FILECURLKHSTAT_FINE_REPLACE,libcurl 会以"整文件重写"方式更新 known_hosts 文件,因此该文件所在目录及文件本身必须有相应写权限,否则写入会失败(libcurl 仅记录infof警告,不会中断连接)。

总结

CURLOPT_SSH_KEYDATA是一个极简但关键的配套选项:它把 libcurl 的主机密钥校验机制从"内置默认行为"扩展为"应用可控策略"。借助它,开发者可以在 SFTP/SCP 场景下实现自定义的 known_hosts 策略(如首连信任、密钥轮换替换、审计日志记录等),而其底层传递路径——从 lib/setopt.c 的存储、lib/urldata.h 的承载,到两个 SSH 后端 lib/vssh/libssh2.c 与 lib/vssh/libssh.c 的调用——构成了一个完整、可追踪的实现闭环。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 8:17:59

Jackett 评分筛选:一次搜出 50 条结果,哪几条值得下载

Jackett 评分筛选&#xff1a;一次搜出 50 条结果&#xff0c;哪几条值得下载 【免费下载链接】Jackett API Support for your favorite torrent trackers 项目地址: https://gitcode.com/GitHub_Trending/ja/Jackett 在 Jackett 里搜一部《沙丘》&#xff0c;回来五十多…

作者头像 李华
网站建设 2026/9/11 8:15:30

豆瓣电影Top250数据爬取与分析全流程实战

1. 项目背景与核心价值电影数据分析一直是互联网内容挖掘的热门方向。豆瓣电影Top250榜单作为中文互联网最具公信力的电影评分集合&#xff0c;包含了大量有价值的结构化数据&#xff1a;从基础的电影名称、评分、评价人数&#xff0c;到导演、主演、类型、制片国家&#xff0c…

作者头像 李华
网站建设 2026/9/11 8:14:46

Java Web开发实战:Thymeleaf+Servlet+MySQL图书管理系统

1. 项目背景与技术选型解析这个基于ThymeleafMavenServletMySQL的图书管理系统框架&#xff0c;是典型的Java Web应用开发实践案例。作为系列教程的第三部分&#xff08;共九章&#xff09;&#xff0c;它聚焦于企业级应用开发中最核心的技术栈组合。为什么选择这个技术组合&am…

作者头像 李华