curl 的 CURLOPT_AWS_SIGV4 选项详解:在 libcurl 中实现 AWS SigV4 请求签名
【免费下载链接】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_AWS_SIGV4是 libcurl(当前仓库 curl 项目)提供的 AWS Signature Version 4(SigV4)认证选项,它能让 curl 自动为 HTTP(S) 请求生成标准化的Authorization头、日期头与载荷哈希,从而直接访问 AWS 及其兼容云服务的签名 API。读完本文,你将掌握该选项的参数格式与推导规则、它与CURLOPT_HTTPAUTH的关系、底层签名计算流程,以及如何配合凭据与自定义头完成可实际运行的请求签名。
该选项由官方文档 CURLOPT_AWS_SIGV4.md 定义,自 curl 7.75.0 起可用,仅适用于 HTTP 协议。下文将以其为核心骨架,结合仓库内 lib/http_aws_sigv4.c 等源码实现逐层展开。
一、选项概览:函数签名、默认值与返回值
函数原型(SYNOPSIS)
#include <curl/curl.h> CURLcode curl_easy_setopt(CURL *handle, CURLOPT_AWS_SIGV4, char *param);默认值(DEFAULT)
默认值为NULL,即默认不启用 AWS SigV4 签名。文档明确说明:
- 应用传入该选项后,无需保留字符串,libcurl 内部会复制它;
- 多次调用该选项时,最后一次设置的值覆盖之前的值;
- 传入
NULL可再次关闭该功能。
返回值(RETURN VALUE)
与所有curl_easy_setopt(3)调用一样,返回CURLcode:CURLE_OK (0)表示成功,非零表示出错(详见 libcurl-errors)。
二、param 参数格式:provider1[:provider2[:region[:service]]]
该选项接受一个字符串指针,其中包含用于生成出站认证头的一系列参数,格式为:
provider1[:provider2[:region[:service]]]四个字段的含义(文档原文要点):
| 字段 | 含义 | 缺省时的处理 |
|---|---|---|
| provider1 | 主提供商标识,参与生成 "Algorithm"、"date"、"request type"、"signed headers" 等认证参数 | 必填 |
| provider2 | 次提供商标识,同样参与上述认证参数的生成 | 省略时复用 provider1 |
| region | 资源集合的地理区域 | 从 URL 主机名中提取 |
| service | 云服务提供的功能名称 | 从 URL 主机名中提取 |
"Test:Try" 示例:每个字段如何影响生成结果
文档给出了非常直观的例子:当参数为"Test:Try"时,curl 使用算法时会生成:
- Algorithm(算法):
"TEST-HMAC-SHA256" - date(日期头):
"x-try-date"与"X-Try-Date" - request type(请求类型):
"test4_request" - signed headers(签名头列表):
"SignedHeaders=content-type;host;x-try-date"
如果只写"test"(省略 provider2),则 test 会被用于每一个生成的字符串,例如算法变为TEST-HMAC-SHA256、日期头变为x-test-date等。
从源码看参数解析规则
在 lib/http_aws_sigv4.c 的parse_sigv4_params(第 806~858 行)中,参数按:依次切分并存入provider0、provider1、region、service四个字段:
- provider0 不能为空,否则返回
CURLE_BAD_FUNCTION_ARGUMENT,并报错first aws-sigv4 provider cannot be empty; - provider1 缺省时直接复用 provider0(对应文档中 "test" 用于所有字符串的说法);
- region / service 若未在参数中给出,则从 URL 主机名提取(见下一节);
- 每个字段最长不超过
MAX_SIGV4_LEN(64 字节,源码第 280 行)。
三、region 与 service 的自动推导:从 URL 主机名提取
当参数中省略 region 或 service 时,它们会从 URL 指定的主机名中提取。文档指出:region 是"资源集合的地理区域",service 是"云提供的一个功能",二者缺省时均从主机名提取。
从源码parse_sigv4_params第 835~855 行可以看到具体推导逻辑:对于形如service.region.example.com的主机名:
- 第一个标签(
.之前)被提取为service; - 若 region 也未给出,则继续取下一个标签作为region;
- 若主机名中无法提取出 service 或 region,分别返回
CURLE_URL_MALFORMAT,并报错aws-sigv4: service missing in parameters and hostname/aws-sigv4: region missing in parameters and hostname; - 成功推导时会输出类似
aws_sigv4: picked service xxx from host的信息日志(infof)。
因此文档示例中https://service.region.example.com/uri配合"provider1:provider2"时,service 与 region 会自动取自主机名;而https://example.com/uri这类无法推导的主机名,就必须在参数里显式给出region:service。
四、与 CURLOPT_HTTPAUTH 的关系:AWS_SIGV4 是一种特殊认证方式
文档特别强调两点:
- 调用本选项会将
CURLOPT_HTTPAUTH(3)设置为CURLAUTH_AWS_SIGV4; - 直接用
CURLOPT_HTTPAUTH(3)设置CURLAUTH_AWS_SIGV4位,等价于用参数"aws:amz"调用本选项。
这两点在 lib/setopt.c 第 2062~2076 行的case CURLOPT_AWS_SIGV4分支中得到印证:
result = Curl_setstropt(data, STRING_AWS_SIGV4, ptr); /* Basic has been set by default; it needs to be unset here. */ if(CURL_EASY_STR(data, STRING_AWS_SIGV4)) s->httpauth = CURLAUTH_AWS_SIGV4; else s->httpauth &= ~(uint32_t)CURLAUTH_AWS_SIGV4;即:设置字符串后直接把httpauth覆盖为CURLAUTH_AWS_SIGV4(源码注释也指出默认的 Basic 认证在此被清除);置空字符串时则清除该认证位。另外该分支被#ifndef CURL_DISABLE_AWS包裹(第 2061 行),意味着编译时可用CURL_DISABLE_AWS宏裁剪掉此功能。
认证头由谁输出
在 lib/http.c 的output_auth_headers(第 662~668 行)中,当选中的认证方式为CURLAUTH_AWS_SIGV4且不是代理请求时,调用Curl_output_aws_sigv4(data)生成认证头:
if((authstatus->picked == CURLAUTH_AWS_SIGV4) && !proxy) { /* this method is never for proxy */ auth = "AWS_SIGV4"; result = Curl_output_aws_sigv4(data); ... }可见 SigV4 签名只对目标服务器生效,从不用于代理。
五、完整示例:最小可运行代码
文档提供了如下示例(此处补充了凭据设置的说明):
int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "https://service.region.example.com/uri"); curl_easy_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2"); /* service and region can also be set in CURLOPT_AWS_SIGV4 */ curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/uri"); curl_easy_setopt(curl, CURLOPT_AWS_SIGV4, "provider1:provider2:region:service"); curl_easy_setopt(curl, CURLOPT_USERPWD, "MY_ACCESS_KEY:MY_SECRET_KEY"); result = curl_easy_perform(curl); curl_easy_cleanup(curl); } }要点:
- 凭据通过
CURLOPT_USERPWD以access_key:secret_key形式提供(源码中签名密钥取自data->state.creds的用户名与密码,见 lib/http_aws_sigv4.c 第 1080、1086 行); - 第一段 URL 的主机名
service.region.example.com可自动推导 region/service,因此参数只写"provider1:provider2"; - 第二段 URL 无法推导,必须在参数中显式给出
region:service。
命令行(curl 工具)中对应写法为:
curl --aws-sigv4 "aws:amz:us-east-1:s3" \ --user "MY_ACCESS_KEY:MY_SECRET_KEY" \ "https://bucket.s3.amazonaws.com/key"六、签名过程源码级剖析
Curl_output_aws_sigv4(lib/http_aws_sigv4.c 第 1149~1230 行)是整个签名的总入口,按顺序完成以下步骤,与 AWS SigV4 规范一一对应:
- 前置检查:若设置了
CURLOPT_PATH_AS_IS(path_as_is),直接报错Cannot use sigv4 authentication with path-as-is flag(第 1174~1177 行);若用户已通过CURLOPT_HTTPHEADER提供了Authorization头,则跳过签名(第 1179~1181 行); - 解析参数:
parse_sigv4_params解析 provider0/provider1/region/service; - 计算载荷哈希:
get_payload_hash; - 生成时间戳:
get_timestamp生成%Y%m%dT%H%M%SZ格式的 16 字节时间戳; - 构造 Canonical Request:
make_canonical_request; - 构造 String to Sign:
make_string_to_sign; - 派生签名密钥并生成认证头:
sign_and_set_auth_headers。
6.1 规范请求(Canonical Request)
make_canonical_request(第 925~986 行)将请求组装为:
HTTPRequestMethod\n CanonicalURI\n CanonicalQueryString\n CanonicalHeaders\n SignedHeaders\n HashedRequestPayload(hex)其中:
- CanonicalURI:由
canon_path(第 670~695 行)生成。默认对路径做 URI 编码(保留 RFC 3986 非保留字符与/),但s3、s3-express、s3-outposts三个服务不做额外 URL 编码(见should_urlencode,第 263~277 行); - CanonicalQueryString:由
canon_query(第 699~804 行)对查询串按&拆分(上限 128 个组件,超出返回CURLE_TOO_LARGE)、逐键值规范化编码,再按键名(其次按值)排序拼接; - CanonicalHeaders:由
make_headers(第 369~535 行)收集Host头、载荷哈希头与用户自定义头,统一小写头名、折叠多余空白、按头名大小写敏感的字母序排序,并合并重名头; - SignedHeaders:规范头列表对应的头名清单,以
;连接。
6.2 待签字符串(String to Sign)
make_string_to_sign(第 988~1065 行)生成:
<Provider0>4-HMAC-SHA256\n <RequestDateTime>\n <CredentialScope>\n <HashedCanonicalRequest>源码细节:
- 算法行形如
%.*s4-HMAC-SHA256,其中 provider0先小写拼接再整体转大写(第 1039~1047、1054~1056 行),这就是文档中"Test:Try"生成TEST-HMAC-SHA256的原因; - 请求类型形如
%.*s4_request(第 1005 行),例如test4_request,即文档所说的 "request type"; - CredentialScope 为
date/region/service/request_type(第 1015~1020 行)。
6.3 签名密钥派生与 Authorization 头
sign_and_set_auth_headers(第 1067~1147 行):
- 签名密钥以
%.*s4+ 密码构造(如AWS4+ secret key),provider0 部分转大写(第 1090~1095 行); - 随后按 AWS 标准做四级 HMAC-SHA256 链式派生(第 1097~1105 行):先对 date 签名、再对 region、service、request_type、最后对 str_to_sign 签名;
- 最终生成认证头并写入
data->req.hd_auth(第 1111~1139 行):
Authorization: <PROVIDER0>4-HMAC-SHA256 Credential=<user>/<scope>, SignedHeaders=<list>, Signature=<hex>\r\n X-<Provider1>-Date: <timestamp>\r\n与文档中 "TEST-HMAC-SHA256"、"x-try-date"/"X-Try-Date" 的生成规律完全吻合。若x-provider1-date头已由用户通过CURLOPT_HTTPHEADER提供(find_date_hdr,第 71~78 行),则复用其时间戳,不再重复添加日期头(第 458~490 行)。
七、载荷哈希与 x-provider2-content-sha256
文档 NOTES 部分说明:签名计算使用请求载荷的 SHA-256 校验和作为输入:
- 对于 POST 请求,是对
CURLOPT_POSTFIELDS(3)提供的请求体计算校验和; - 其他请求(如 GET)使用空缓冲区的校验和;
- 对于 PUT 等请求,可以自行在名为x-provider2-content-sha256的 HTTP 头中提供校验和。
源码对应实现:
calc_payload_hash(第 574~592 行):对data->set.postfields计算 SHA-256 并转十六进制;postfieldsize < 0时按strlen计算长度;parse_content_sha_hdr(第 542~572 行):先查找形如x-provider2-content-sha256的用户头,找到则直接采用其值,不再自行计算(get_payload_hash第 860~896 行);- 载荷哈希以十六进制串形式出现在 Canonical Request 的最后一行,以及
Authorization头的签名计算输入中。
对 aws:s3 的特殊处理:x-amz-content-sha256 与 UNSIGNED-PAYLOAD
文档特别指出:对于aws:s3,若请求中尚不存在该头,curl 会为每个请求添加x-amz-content-sha256头;当 S3 请求载荷未知时,该头取特殊值"UNSIGNED-PAYLOAD"。
源码中:
get_payload_hash第 879~885 行判断provider0 == "aws"且service == "s3"时走calc_s3_payload_hash(第 596~631 行);- 当请求方法为 GET/HEAD(
empty_method)、无文件载荷(filesize == 0)或 POST 且载荷在内存中时,仍计算真实哈希;其他情况回退为字符串UNSIGNED-PAYLOAD(宏S3_UNSIGNED_PAYLOAD,第 594 行); - 生成的
x-<provider1>-content-sha256: <hash>头会附加到签名头列表与最终请求中。
八、与 CURLOPT_HTTPHEADER / CURLOPT_HEADEROPT 的配合
签名过程需要读取用户自定义头,因此理解 CURLOPT_HTTPHEADER 的语义有助于正确使用本选项:
Authorization:用户显式提供后,libcurl 直接跳过 SigV4 生成(lib/http_aws_sigv4.c 第 1179~1181 行);x-provider1-date:用户提供后作为时间戳来源,不再自动生成日期头(find_date_hdr);x-provider2-content-sha256:用户提供后作为载荷哈希来源,PUT 等请求可借此对未知载荷做自定义哈希;Host:若用户未提供,签名头中自动加入Host: [host]:[port](make_headers第 397~408 行);- 用户头中形如
name:(无值)用于移除同名内部头、name;用于发送空值头的处理规则同样适用于签名头收集(第 417~454 行注释)。
若需在签名头的生成中统一控制这些头的处理方式,可参考 CURLOPT_HEADEROPT。
九、注意事项与限制(NOTES)
综合文档与源码,使用本选项必须注意:
- 覆盖其他认证方式:设置本选项会覆盖
CURLOPT_HTTPAUTH(3)中可能设置的其他认证类型(lib/setopt.c 第 2062~2074 行),且该认证方式不能与其他认证类型组合使用; - 仅限非代理:签名头只发给目标服务器,不会用于代理认证(lib/http.c 第 662 行);
- 与 CURLOPT_PATH_AS_IS 冲突:设置
CURLOPT_PATH_AS_IS后使用 SigV4 会返回CURLE_BAD_FUNCTION_ARGUMENT(第 1174~1177 行); - 凭据来源:
CURLOPT_USERPWD的user:password分别作为 access key 与 secret key; - 时间戳:默认取当前 UTC 时间,格式
YYYYMMDDTHHMMSSZ;调试构建下可用环境变量CURL_FORCETIME强制固定时间(get_timestamp第 904~911 行),这也是测试套件依赖 Debug 特性固定时间的原因。
十、测试验证:仓库如何保证实现正确
仓库为 SigV4 提供了大量测试,可作为验证与学习参照:
- tests/data/test1933:使用单 provider 并通过 URL 携带凭据,验证生成的请求头。其期望输出(见
<verify>段)展示了完整结果:
GET /%TESTNUMBER/testapi/test HTTP/1.1 Host: 127.0.0.1:9000 Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-xxx-date, Signature=3d8e00a02e437211a596143dcd590fcc805b731365c68f7f48951ea6eda39c4f X-Xxx-Date: 19700101T000000Z可以看到XXX4-HMAC-SHA256、xxx4_request、x-xxx-date与X-Xxx-Date的生成规律和文档中 "Test:Try" 的推导规则完全一致;
- tests/data/test1979 与 tests/data/test1980:分别对
canon_path与canon_query做单元测试(源码中对应函数标注了/* @unittest 1979 */与/* @unittest 1980 */); - 其余如 tests/data/test1934~tests/data/test1938、tests/data/test1955~tests/data/test1959 等覆盖了多 provider、S3、代理、重定向等不同场景。
十一、相关选项速查
| 选项 | 作用 |
|---|---|
| CURLOPT_HTTPAUTH | 设置认证方式,CURLAUTH_AWS_SIGV4与本选项等价 |
| CURLOPT_HTTPHEADER | 自定义 HTTP 头,可覆盖 Authorization、日期头、载荷哈希头 |
| CURLOPT_HEADEROPT | 控制自定义头是否作用于代理请求 |
| CURLOPT_PROXYAUTH | 代理认证方式(SigV4 不用于代理) |
结语
CURLOPT_AWS_SIGV4把 AWS SigV4 的完整签名流程(Canonical Request 构造、String to Sign 生成、HMAC 密钥派生、认证头输出)封装进 curl 内部。只需提供 provider 组合、凭据与目标 URL,libcurl 就会自动推导 region/service、计算载荷哈希并生成符合规范的签名请求;S3 等特殊服务还有UNSIGNED-PAYLOAD与自动x-amz-content-sha256的适配。对于需要对接 AWS 或兼容 SigV4 云服务的应用,这是比手动实现签名更可靠、更省力的选择。
【免费下载链接】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),仅供参考