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。- 返回值:
CURLcode,CURLE_OK (0)表示设置成功,非零值表示出错(参见 libcurl-errors)。
在 libcurl 的选项注册表 lib/easyoptions.c 中,该选项的类型被登记为CURLOT_CBPTR(callback pointer),说明它属于"伴随回调使用的指针"一类,与SSH_KEYFUNCTION(CURLOT_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。当未设置该选项时,clientp为NULL——所以回调内部要做空指针防御。
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); } }示例要点:
callback_data是栈上分配的结构体,通过&callback_data传入CURLOPT_SSH_KEYDATA;- 回调中通过
clientp拿回该结构体指针,即可访问应用自定义数据; CURLOPT_SSH_KNOWNHOSTS必须一并设置,否则回调不会被触发(详见 CURLOPT_SSH_KEYFUNCTION 中 "The callback is only called if CURLOPT_SSH_KNOWNHOSTS(3) is also set" 的说明);- 回调返回
CURLKHSTAT_FINE_ADD_TO_FILE,表示信任该主机并把密钥写入 known_hosts(典型的 "trust on first use" 场景)。
与相关选项的组合使用
从 lib/vssh/libssh.c 的注释可以看到,libcurl 对 SSH 主机密钥校验提供了一条优先级链:
- 设置了
CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256:按 SHA256 哈希校验; - 设置了
CURLOPT_SSH_HOST_PUBLIC_KEY_MD5:按 MD5 哈希校验; - 设置了
CURLOPT_SSH_KEYFUNCTION回调:做 trust-on-first-use,若回调返回CURLKHSTAT_FINE_ADD_TO_FILE还会写入 known_hosts; - 以上均未设置:仅当主机已存在于 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),并期望连接以错误码60(CURLE_PEER_FAILED_VERIFICATION)失败。这从侧面印证了 known_hosts 校验失败时的行为路径:即使不经过用户回调,libcurl 也会在密钥不匹配时拒绝继续连接。而一旦你设置了CURLOPT_SSH_KEYFUNCTION+CURLOPT_SSH_KEYDATA,这个"拒绝"的决策权就交还给了你的回调。
默认值与返回值
- 默认值:
NULL。未设置时,回调收到的clientp为NULL,回调中需自行处理。 curl_easy_setopt返回值:CURLE_OK (0)表示设置成功,非零表示错误,具体错误码参见 libcurl-errors。
实践建议
- 生命周期管理:
CURLOPT_SSH_KEYDATA只保存指针,不拷贝内容。传入的数据必须保证在curl_easy_perform返回之前一直有效(栈上结构体、静态变量、堆上对象均可,但不要在回调返回后继续使用被释放的指针)。 - 空指针防御:回调第一个参数
easy、knownkey(在主机不在 known_hosts 中时为NULL)以及clientp(未设置 KEYDATA 时)都可能为NULL,引用前务必判空。 - 多线程注意:该选项是 easy handle 级配置,不同 easy handle 之间互不影响;但同一 handle 不应在多线程中并发执行
curl_easy_perform。 - 文件写入权限:若回调返回
CURLKHSTAT_FINE_ADD_TO_FILE或CURLKHSTAT_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),仅供参考