AWS V4签名实现(C版本)

如果要使用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上找轮子。

1 git提供的方案

发现一个非常好的轮子,感觉比我写的专业多了,用着好用记得start,强势引流。
项目地址:
https://github.com/sidbai/aws-sigv4-c
Git:
https://github.com/sidbai/aws-sigv4-c.git

2 理论基础

2.1 签名步骤

1 Canonitcal Requet
2 StringToSign
3 Signature
整个流程构成是很复杂的,具体详细每一步中执行了什么操作,可参考另一篇JavaScript实现签名的文章,里面进行了详细说明,这里就不重复写了。https://blog.csdn.net/m0_37263637/article/details/79553560

2.2 AWS V4签名流程图

这里写图片描述

3 sample

本流程是使用该代码进行签名然后进行S3 put测试。
环境: linux(ubuntu)
语言: C
依赖:curl ssl crypto aws-sigv4-c

3.1 Git 下载代码配置环境

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签名部分的核心代码。该项目非常靠谱的提供了测试代码,我们可以参考它测试代码进行实现。

3.2 sample code

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <curl/curl.h>
#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;
    }
}

3.3 相关函数说明

本文使用C签名lib来自
https://github.com/sidbai/aws-sigv4-c

3.3.1 配置

 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服务获取临时凭证

3.3.2 如何签名

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最近才发布,实际使用上发现一些问题,所以对源码做了一些修改

3.3.2.1 query支持问题

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参数。接口传入也不会处理。

3.3.2.2 负载hash的问题

虽然我在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);

3.3.3 关于请求

使用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

3.3.4 关于AWS 临时凭证签名

我们可以通过coginito服务身份池服务得到AWS 临时凭证。
临时凭证由3部分组成:

  • access_keyid
  • secret_access_key
  • session_token
    key 和id 我们用于像正常过程那样签名。
    但临时凭证请求需要带上session_token

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);

4 sample git地址

https://github.com/CollapsarLi/aws_v4_signature_c_sample

猜你喜欢

转载自blog.csdn.net/m0_37263637/article/details/81283521
AWS
今日推荐