AWS 署名 version4 の C 言語実装を読んでみた

以前、紹介した aws_dynamo に C 言語による aws 署名 version4 の実装が含まれているため、ソースコードリーディングしてみました。

DynamoDB を aws_dynamo から触ってみた

公式ドキュメントにあるとおり aws 署名 version4 は 3 つの手順からなります。

  1. 正規リクエストの作成
  2. 署名文字列の作成
  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);
}
[/c]

<h2 id="toc-1">署名文字列の作成</h2>

<a href="http://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-string-to-sign.html" target="_blank">タスク 2: 署名バージョン 4 の署名文字列を作成する</a> は以下の関数で実装されています。


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]

まとめ

C 言語の場合、特に文字列操作周りでコード量が多くなりがちにはなりますが、aws にネイティブコードで触ることができるのは特徴だと思いますので、興味のある方は触ってみてはいかがでしょうか。