44#include <openssl/sha.h>
45#include <openssl/hmac.h>
48#include "BESInternalError.h"
52#define prolog std::string("AWSV4::").append(__func__).append("() - ")
57const int SHA256_DIGEST_STRING_LENGTH = (SHA256_DIGEST_LENGTH << 1);
65std::string join(
const std::vector<std::string> &ss,
const std::string &delim) {
69 std::stringstream sstream;
70 const size_t l = ss.size() - 1;
71 for (
size_t i = 0; i < l; i++) {
72 sstream << ss[i] << delim;
78std::string sha256_base16(
const std::string &str) {
80 unsigned char hashOut[SHA256_DIGEST_LENGTH];
83 SHA256_Update(&sha256, (
const unsigned char *) str.c_str(), str.size());
84 SHA256_Final(hashOut, &sha256);
86 char outputBuffer[SHA256_DIGEST_STRING_LENGTH + 1];
87 for (
int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
88 snprintf(outputBuffer + (i * 2), 3,
"%02x", hashOut[i]);
90 outputBuffer[SHA256_DIGEST_STRING_LENGTH] = 0;
91 return std::string{outputBuffer};
95static std::string trim(
const std::string &str,
const std::string &whitespace =
" \t") {
96 const auto strBegin = str.find_first_not_of(whitespace);
97 if (strBegin == std::string::npos)
100 const auto strEnd = str.find_last_not_of(whitespace);
101 const auto strRange = strEnd - strBegin + 1;
103 return str.substr(strBegin, strRange);
114std::map<std::string, std::string> canonicalize_headers(
const std::vector<std::string> &headers) {
115 std::map<std::string, std::string> header_key2val;
116 for (
const auto &header: headers) {
117 auto i = header.find(
':');
118 if (i == std::string::npos) {
119 header_key2val.clear();
120 return header_key2val;
123 std::string key = trim(header.substr(0, i));
124 const std::string val = trim(header.substr(i + 1));
125 if (key.empty() || val.empty()) {
126 header_key2val.clear();
127 return header_key2val;
130 std::transform(key.begin(), key.end(), key.begin(), ::tolower);
131 header_key2val[key] = val;
133 return header_key2val;
137std::string map_headers_string(
const std::map<std::string, std::string> &header_key2val) {
138 const std::string pair_delim{
":"};
140 for (
const auto &kv: header_key2val) {
141 h.append(kv.first).append(pair_delim).append(kv.second).append(ENDL);
147std::string map_signed_headers(
const std::map<std::string, std::string> &header_key2val) {
148 const std::string signed_headers_delim{
";"};
149 std::vector<std::string> ks;
150 for (
const auto &kv: header_key2val) {
151 ks.push_back(kv.first);
153 return join(ks, signed_headers_delim);
156std::string canonicalize_request(
const std::string &http_request_method,
157 const std::string &canonical_uri,
158 const std::string &canonical_query_string,
159 const std::string &canonical_headers,
160 const std::string &signed_headers,
161 const std::string &shar256_of_payload) {
162 return std::string(http_request_method).append(ENDL).append(canonical_uri).append(ENDL)
163 .append(canonical_query_string).append(ENDL).append(canonical_headers).append(ENDL)
164 .append(signed_headers).append(ENDL).append(shar256_of_payload);
171std::string string_to_sign(
const std::string &algorithm,
172 const std::time_t &request_date,
173 const std::string &credential_scope,
174 const std::string &hashed_canonical_request) {
175 return algorithm + ENDL +
176 ISO8601_date(request_date) + ENDL +
177 credential_scope + ENDL +
178 hashed_canonical_request;
181std::string credential_scope(
const std::time_t &request_date,
182 const std::string ®ion,
183 const std::string &service) {
184 const std::string s{
"/"};
185 return utc_yyyymmdd(request_date).append(s).append(region).append(s).append(service).append(s).append(AWS4_REQUEST);
189std::string ISO8601_date(
const std::time_t &t) {
190 char buf[
sizeof "20111008T070709Z"];
192 std::strftime(buf,
sizeof buf,
"%Y%m%dT%H%M%SZ", gmtime_r(&t, &tm_buf));
197std::string utc_yyyymmdd(
const std::time_t &t) {
198 char buf[
sizeof "20111008"];
200 std::strftime(buf,
sizeof buf,
"%Y%m%d", gmtime_r(&t, &tm_buf));
205std::string hmac_to_string(
const unsigned char *hmac) {
207 char buf[SHA256_DIGEST_STRING_LENGTH + 1];
208 for (
int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
210 snprintf(buf + (i * 2), 3,
"%02x", hmac[i]);
212 buf[SHA256_DIGEST_STRING_LENGTH] = 0;
228std::string calculate_signature(
const std::time_t &request_date,
229 const std::string &secret,
230 const std::string ®ion,
231 const std::string &service,
232 const std::string &string_to_sign) {
235 unsigned char md[EVP_MAX_MD_SIZE + 1];
238 const std::string k1 = AWS4 + secret;
239 const std::string yyyymmdd = utc_yyyymmdd(request_date);
241 const unsigned char *kDate = HMAC(EVP_sha256(), (
const void *)k1.c_str(), (
unsigned int)k1.size(),
242 (
const unsigned char *) yyyymmdd.c_str(), yyyymmdd.size(), md, &md_len);
244 throw BESInternalError(
"Could not compute AWS V4 request signature.", __FILE__, __LINE__);
247 BESDEBUG(HTTP_MODULE,
248 prolog <<
"kDate: " << hmac_to_string(kDate) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md)
251 const unsigned char *kRegion = HMAC(EVP_sha256(), md, md_len,
252 (
const unsigned char *) region.c_str(), region.size(), md, &md_len);
254 throw BESInternalError(
"Could not compute AWS V4 request signature.", __FILE__, __LINE__);
257 BESDEBUG(HTTP_MODULE,
258 prolog <<
"kRegion: " << hmac_to_string(kRegion) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md)
261 const unsigned char *kService = HMAC(EVP_sha256(), md, md_len,
262 (
const unsigned char *) service.c_str(), service.size(), md, &md_len);
264 throw BESInternalError(
"Could not compute AWS V4 request signature.", __FILE__, __LINE__);
267 BESDEBUG(HTTP_MODULE, prolog <<
"kService: " << hmac_to_string(kService) <<
" md_len: " << md_len <<
" md: "
268 << hmac_to_string(md) << std::endl);
270 const unsigned char *kSigning = HMAC(EVP_sha256(), md, md_len,
271 (
const unsigned char *) AWS4_REQUEST.c_str(), AWS4_REQUEST.size(), md, &md_len);
273 throw BESInternalError(
"Could not compute AWS V4 request signature.", __FILE__, __LINE__);
276 BESDEBUG(HTTP_MODULE,
277 prolog <<
"kSigning: " << hmac_to_string(kRegion) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md)
280 const unsigned char *kSig = HMAC(EVP_sha256(), md, md_len,
281 (
const unsigned char *) string_to_sign.c_str(), string_to_sign.size(), md, &md_len);
283 throw BESInternalError(
"Could not compute AWS V4 request signature.", __FILE__, __LINE__);
286 auto sig = hmac_to_string(md);
287 BESDEBUG(HTTP_MODULE, prolog <<
"kSig: " << sig <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl);
305std::string compute_awsv4_signature(
const std::string &canonical_uri,
const std::string &canonical_query,
306 const std::string &host,
const std::time_t &request_date,
307 const std::string &public_key,
const std::string &secret_key,
308 const std::string ®ion,
const std::string &service) {
311 const std::string sha256_empty_payload = {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"};
319 std::vector<std::string> headers{
"host:",
"x-amz-date:"};
320 headers[0].append(host);
321 headers[1].append(ISO8601_date(request_date));
323 const auto canonical_headers_map = canonicalize_headers(headers);
324 if (canonical_headers_map.empty()) {
325 throw std::runtime_error(
"Empty header list while building AWS V4 request signature");
327 const auto headers_string = map_headers_string(canonical_headers_map);
328 const auto signed_headers = map_signed_headers(canonical_headers_map);
329 const auto canonical_request = canonicalize_request(AWSV4::GET,
334 sha256_empty_payload);
336 BESDEBUG(HTTP_MODULE, prolog <<
"Canonical Request: " << canonical_request << std::endl);
338 auto hashed_canonical_request = sha256_base16(canonical_request);
339 auto credential_scope = AWSV4::credential_scope(request_date, region, service);
340 auto string_to_sign = AWSV4::string_to_sign(STRING_TO_SIGN_ALGO,
343 hashed_canonical_request);
345 BESDEBUG(HTTP_MODULE, prolog <<
"String to Sign: " << string_to_sign << std::endl);
347 auto signature = calculate_signature(request_date,
353 BESDEBUG(HTTP_MODULE, prolog <<
"signature: " << signature << std::endl);
355 std::string authorization_header = STRING_TO_SIGN_ALGO +
" Credential=" + public_key +
"/"
356 + credential_scope +
", SignedHeaders=" + signed_headers +
", Signature=" +
359 BESDEBUG(HTTP_MODULE, prolog <<
"authorization_header: " << authorization_header << std::endl);
361 return authorization_header;
376std::string compute_awsv4_signature(
377 const http::url &uri,
378 const std::time_t &request_date,
379 const std::string &public_key,
380 const std::string &secret_key,
381 const std::string ®ion,
382 const std::string &service) {
383 return compute_awsv4_signature(uri.path(), uri.query(), uri.host(), request_date, public_key, secret_key,