如果要使用AWS提供各种服务的restful API 都不能避开AWS V4签名。(当然你可以使用sdk,简单粗暴)
AWS V4签名逻辑文档:
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sig-v4-authenticating-requests.html
JavaScript实现签名流程:
https://blog.csdn.net/m0_37263637/article/details/79553560
最近必须要用restful API 实现S3上传,而环境又是C(嵌入式平台不支持C++),所以必须想办法实现了,但过程比较复杂繁琐,所以非常自然的去git上找轮子。
发现一个非常好的轮子,感觉比我写的专业多了,用着好用记得start,强势引流。
项目地址:
https://github.com/sidbai/aws-sigv4-c
Git:
https://github.com/sidbai/aws-sigv4-c.git
1 Canonitcal Requet
2 StringToSign
3 Signature
整个流程构成是很复杂的,具体详细每一步中执行了什么操作,可参考另一篇JavaScript实现签名的文章,里面进行了详细说明,这里就不重复写了。https://blog.csdn.net/m0_37263637/article/details/79553560
本流程是使用该代码进行签名然后进行S3 put测试。
环境: linux(ubuntu)
语言: C
依赖:curl ssl crypto aws-sigv4-c
git clone https://github.com/sidbai/aws-sigv4-c.git
cd aws_sigv4
cd lib
看到aws_sigv4.c aws_sigv4_common.c aws_sigv4_common.h aws_sigv4.h 这四个文件,就是AWS V4 C签名部分的核心代码。该项目非常靠谱的提供了测试代码,我们可以参考它测试代码进行实现。
#include
#include
#include
#include
#include
#include
#include
#include
#include "aws_sigv4.h"
//aws Signature(V4)source from:https://github.com/sidbai/aws-sigv4-c.git
//this is sample for test
long put(char* url, FILE* fd, int fsize, struct curl_slist *headers,char* response);
//curl put callback for response
size_t write_data(char* buffer, size_t size, size_t nmemb, void* userp){
memcpy(userp, buffer, size * nmemb);
return nmemb;
}
//curl put callback for send file data
size_t read_data(char* buffer, size_t size, size_t nitems, void* instream){
size_t sizes = fread(buffer, size, nitems, (FILE *)instream);
return nitems;
}
//get aws v4 struct
static inline aws_sigv4_str_t construct_str(const unsigned char* cstr)
{
aws_sigv4_str_t ret = { .data = NULL, .len = 0 };
if (cstr)
{
ret.data = (unsigned char*) cstr;
ret.len = strlen(cstr);
}
return ret;
}
//get aws v4 date param
int getTime(char* timestr)
{
time_t timep = time(NULL);
struct tm* utcTime =gmtime(&timep);
sprintf(timestr, "%04d%02d%02dT%02d%02d%02dZ", utcTime->tm_year+1900, utcTime->tm_mon+1, utcTime->tm_mday, utcTime->tm_hour, utcTime->tm_min, utcTime->tm_sec);
return 0;
}
int main(int argc, char** argv){
//**************config your environment**************//
char url[150] = "http://";
char url_host[50] = "xxxxxxxx.s3.amazonaws.com";// s3 bucket url
char url_request[80] = "xxxxxxxxxxx.s3.ap-northeast-2.amazonaws.com";// s3 bucket endpoint url
char target_path[100] = "/myimage.jpg"; // s3 object path
char *aws_secret_access_key ="XXXXXXXXXXXXXXXXXXXXXXXXXXX";//aws access key
char *aws_access_key_id ="XXXXXXXXXXXXXXXXX"; // aws access_key_id
char *aws_region ="ap-northeast-2";//aws region
char *aws_service ="s3";
char *file_path_local = "./00152212.jpg";
//**************config your environment end**************//
char response[100000];
char imageBuffer[100000];
char time[20];
FILE* r_file = fopen(file_path_local, "rb");
if (0 == r_file)
{
printf( "the file %s isnot exit\n",argv[2]);
return -1;
}
fseek(r_file, 0, 2);
int file_size = ftell(r_file);
rewind(r_file);
getTime(time);
strcat(url, url_request);
strcat(url, target_path);
printf("s3 put url :%s\n", url);
printf("request date :%s\n", time);
//aws_v4_Signature code: not support query(aws_sigv4.c(104)) and no get_hex_sha256(payload hash)(aws_sigv4.c(123))
aws_sigv4_params_t sigv4_params = {.secret_access_key = construct_str(aws_secret_access_key),
.access_key_id = construct_str(aws_access_key_id),
.method = construct_str("PUT"),
.host = construct_str(url_host),
.x_amz_date = construct_str(time),
.uri = construct_str(target_path),
.query_str = construct_str(" "),
.payload = construct_str(NULL),
.region = construct_str(aws_region),
.service = construct_str(aws_service) };
aws_sigv4_header_t auth_header = {.name = construct_str(NULL), .value=construct_str(NULL) };
int rc = aws_sigv4_sign(&sigv4_params, &auth_header);
printf("sigv4 :%s\n",auth_header.value.data);
printf("*************************\n");
char request_date[30] = "x-amz-date: ";
char request_Authorization[200] = "Authorization: ";
char request_host[100] = "host: ";
strcat(request_date, time);
strcat(request_Authorization, auth_header.value.data);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "x-amz-content-sha256: UNSIGNED-PAYLOAD");
headers = curl_slist_append(headers, strcat(request_host, url_host));
headers = curl_slist_append(headers, request_date);
headers = curl_slist_append(headers, request_Authorization);
int status_code = put(url, r_file, file_size, headers, response);// s3 restful api (PUT)
if ((status_code != CURLE_OK)&&(status_code != 200)) {
return -1;
}
else{
printf("response code:%d\n", status_code);
printf("RES :%s\n", response);
}
return 0;
}
//cuel put
long put(char* url, FILE* fd, int fsize, struct curl_slist *headers,char* response)
{
CURL *curl;
curl = curl_easy_init();
long response_code = 0;
if (curl)
{
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); //改协议头
curl_easy_setopt(curl, CURLOPT_READFUNCTION, &read_data);
curl_easy_setopt(curl, CURLOPT_READDATA, fd);
curl_easy_setopt(curl, CURLOPT_INFILESIZE, fsize); //上传的字节数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response);
CURLcode ret = curl_easy_perform(curl); //执行请求
if(ret == 0){
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
curl_easy_cleanup(curl);
return 0;
}
else{
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
return response_code;
}
}
else{
return -1;
}
}
本文使用C签名lib来自
https://github.com/sidbai/aws-sigv4-c
char url_host[50] = "xxxxxxxx.s3.amazonaws.com";// s3 bucket url
char url_request[80] = "xxxxxxxxxxx.s3.ap-northeast-2.amazonaws.com";// s3 bucket endpoint url
char target_path[100] = "/myimage.jpg"; // s3 object path
char *aws_secret_access_key ="XXXXXXXXXXXXXXXXXXXXXXXXXXX";//aws access key
char *aws_access_key_id ="XXXXXXXXXXXXXXXXX"; // aws access_key_id
char *aws_region ="ap-northeast-2";//aws region
char *aws_service ="s3";
char *file_path_local = "./00152212.jpg";
url_host: s3 存储桶地址
url_request:S3 节点地址target_path:上传文件在存储桶的相对路径
ws_secret_access_key:aws凭证密匙
aws_access_key_id :aws凭证ID
aws_region:s3存储桶区域
aws_service :服务名
file_path_local :本地文件名
AWS可以使用coginito服务获取临时凭证
aws_sigv4_params_t sigv4_params = {.secret_access_key = construct_str(aws_secret_access_key),
.access_key_id = construct_str(aws_access_key_id),
.method = construct_str("PUT"),
.host = construct_str(url_host),
.x_amz_date = construct_str(time),
.uri = construct_str(target_path),
.query_str = construct_str(" "),
.payload = construct_str(NULL),
.region = construct_str(aws_region),
.service = construct_str(aws_service) };
aws_sigv4_header_t auth_header = {.name = construct_str(NULL), .value=construct_str(NULL) };
int rc = aws_sigv4_sign(&sigv4_params, &auth_header);
lib 提供了签名接口 通过配置sigv4_params 参数并调用aws_sigv4_sign完成签名
但因lib最近才发布,实际使用上发现一些问题,所以对源码做了一些修改
query_str必须带参数,如果设置为空,会无法完成签名,put应该可以不带query,使用这个lib主要是为了上海窜文件 所以把query参数给屏蔽加了,修改了下列源码。
get_canonical_request函数中
str += aws_sigv4_sprintf(str, "%V\n%V\n%V\n",
&sigv4_params->method,
&sigv4_params->uri,
&sigv4_params->query_str);
修改为:
str += aws_sigv4_sprintf(str, "%V\n%V\n\n",
&sigv4_params->method,
&sigv4_params->uri,
&sigv4_params->query_str);
即默认不带query参数。接口传入也不会处理。
虽然我在param payload传入参数为NULL 但实际上仍然在hash负载,导致AWS校验不过,所以这里修改了这部分源码
get_canonical_request函数中:
aws_sigv4_str_t hex_sha256 = { .data = str, .len = 0 };
get_hex_sha256(&sigv4_params->payload, &hex_sha256);
tr += hex_sha256.len;
修改为了
char payload[] = "UNSIGNED-PAYLOAD";
int payloadlen = strlen(payload);
使用libcurl 发起S3 restful API 请求
S3 restful API 可参考:
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/RESTObjectPUT.html
PUT object请求格式如下:
PUT /ObjectName HTTP/1.1
Host: BucketName.s3.amazonaws.com
Date: date
Authorization: authorization string (see Authenticating Requests (AWS Signature Version
4))
Curl 使用可参考:
https://blog.csdn.net/m0_37263637/article/details/79489321
我们可以通过coginito服务身份池服务得到AWS 临时凭证。
临时凭证由3部分组成:
1 http请求需要带上下列header:
x-amz-security-token: session_token
以下部分需要修改签名lib:
2 签名Canonitcal Requet(signed_headers)
这一步需要带上x-amz-security-token头,即
"host;x-amz-date;x-amz-security-token"
3签名Canonitcal Requet (canonical_headers)
需要加上
canonical_headers->len = aws_sigv4_sprintf(canonical_headers->data, "host:%V\nx-amz-date:%V\nx-amz-security-token:%V\n",
&sigv4_params->host, &sigv4_params->x_amz_date, &sigv4_params->security_token);
https://github.com/CollapsarLi/aws_v4_signature_c_sample