この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
以前、紹介した aws_dynamo に C 言語による aws 署名 version4 の実装が含まれているため、ソースコードリーディングしてみました。
公式ドキュメントにあるとおり aws 署名 version4 は 3 つの手順からなります。
- 正規リクエストの作成
- 署名文字列の作成
- 署名の作成
正規リクエストの作成
タスク 1: 署名バージョン 4 の正規リクエストを作成する は以下の関数で実装されています。
公式ドキュメントにある擬似コードをそれぞれ対応する箇所にコメントとして入れています。
C 言語ではハッシュの計算には OpenSSH ライブラリを使用することが多いです。
char *aws_sigv4_create_hashed_canonical_request(const char *http_request_method,
const char *canonical_uri,
const char *canonical_query_string,
const char *canonical_headers,
const char *signed_headers,
const char *request_payload)
{
SHA256_CTX ctx;
unsigned char hash[SHA256_DIGEST_LENGTH];
int i;
unsigned char hex_hash[SHA256_DIGEST_LENGTH * 2 + 1];
char *canonical_request;
/*
HexHash = HexEncode(Hash(RequestPayload))
*/
SHA256_Init(&ctx);
SHA256_Update(&ctx, request_payload, strlen(request_payload));
SHA256_Final(hash, &ctx);
/*
1 byte ずつ地道に 16 進文字表現を生成する
*/
for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hex_hash + i * 2, "%.2x", hash[i]);
}
/*
CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HexHash
*/
if (asprintf(&canonical_request,
"%s\n%s\n%s\n%s\n%s\n%s",
http_request_method,
canonical_uri,
canonical_query_string,
canonical_headers,
signed_headers,
hex_hash) == -1) {
Warnx("aws_dynamo_create_signature: failed to create message");
return NULL;
}
/*
HashedCanonicalRequest = HexEncode(Hash(CanonicalRequest))
*/
SHA256_Init(&ctx);
SHA256_Update(&ctx, canonical_request, strlen(canonical_request));
SHA256_Final(hash, &ctx);
free(canonical_request);
for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hex_hash + i * 2, "%.2x", hash[i]);
}
return strdup(hex_hash);
}
署名文字列の作成
タスク 2: 署名バージョン 4 の署名文字列を作成する は以下の関数で実装されています。
char *aws_sigv4_create_string_to_sign(char *iso8601_basic_date,
char *yyyy_mm_dd,
const char *region,
const char *service,
const char *hashed_canonical_request)
{
char *string_to_sign;
/*
StringToSign =
Algorithm + '\n' +
RequestDate + '\n' +
CredentialScope + '\n' +
HashedCanonicalRequest
*/
if (asprintf(&string_to_sign,
"AWS4-HMAC-SHA256\n%s\n%s/%s/%s/aws4_request\n%s",
iso8601_basic_date,
yyyy_mm_dd, region, service,
hashed_canonical_request) == -1) {
Warnx("aws_dynamo_create_signature: failed to create string to sign.");
return NULL;
}
return string_to_sign;
}
署名の作成
タスク 3: AWS 署名バージョン 4 を計算する は以下の関数で実装されています。
int aws_sigv4_derive_signing_key(
const char *aws_secret_access_key,
const char *yyyy_mm_dd,
const char *region,
const char *service,
unsigned char **key /* OUT */,
int *key_len /* OUT */)
{
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len;
unsigned char date_key[128];
int n;
HMAC_CTX hmac_ctx;
HMAC_CTX_init(&hmac_ctx);
n = snprintf(date_key, sizeof(date_key), "AWS4%s",
aws_secret_access_key);
if (n < 0 || n > sizeof(date_key)) {
Warnx("aws_sigv4_derive_signing_key: did not create date key.");
return -1;
}
/*
kDate = HMAC("AWS4" + kSecret, Date)
*/
HMAC_Init_ex(&hmac_ctx, date_key, strlen(date_key), EVP_sha256(), NULL);
HMAC_Update(&hmac_ctx, yyyy_mm_dd, strlen(yyyy_mm_dd));
HMAC_Final(&hmac_ctx, md, &md_len);
/*
kRegion = HMAC(kDate, Region)
*/
HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL);
HMAC_Update(&hmac_ctx, region, strlen(region));
HMAC_Final(&hmac_ctx, md, &md_len);
/*
kService = HMAC(kRegion, Service)
*/
HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL);
HMAC_Update(&hmac_ctx, service, strlen(service));
HMAC_Final(&hmac_ctx, md, &md_len);
/*
kSigning = HMAC(kService, "aws4_request")
*/
HMAC_Init_ex(&hmac_ctx, md, md_len, EVP_sha256(), NULL);
HMAC_Update(&hmac_ctx, "aws4_request", strlen("aws4_request"));
HMAC_Final(&hmac_ctx, md, &md_len);
HMAC_CTX_cleanup(&hmac_ctx);
*key = malloc(md_len);
if (*key == NULL) {
Warnx("aws_sigv4_derive_signing_key: key alloc failed.");
return -1;
}
memcpy(*key, md, md_len);
*key_len = md_len;
return 0;
}
char *aws_sigv4_create_signature(
const char *aws_secret_access_key,
const char *yyyy_mm_dd,
const char *region,
const char *service,
const unsigned char *message)
{
HMAC_CTX hmac_ctx;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int md_len;
char *signature;
int message_len = strlen(message);
unsigned char *key = NULL;
int key_len = 0;
int i;
HMAC_CTX_init(&hmac_ctx);
aws_sigv4_derive_signing_key(aws_secret_access_key,
yyyy_mm_dd, region, service, &key, &key_len);
/*
signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
*/
HMAC_Init_ex(&hmac_ctx, key, key_len, EVP_sha256(), NULL);
HMAC_Update(&hmac_ctx, message, message_len);
HMAC_Final(&hmac_ctx, md, &md_len);
free(key);
signature = malloc(md_len * 2 + 1);
if (signature == NULL) {
Warnx("aws_sigv4_create_signature: failed to allocate sig");
HMAC_CTX_cleanup(&hmac_ctx);
return NULL;
}
for (i = 0; i < md_len; i++) {
sprintf(signature + i * 2, "%.2x", md[i]);
}
HMAC_CTX_cleanup(&hmac_ctx);
return signature;
}
まとめ
C 言語の場合、特に文字列操作周りでコード量が多くなりがちにはなりますが、aws にネイティブコードで触ることができるのは特徴だと思いますので、興味のある方は触ってみてはいかがでしょうか。