44#include <openssl/sha.h>
45#include <openssl/hmac.h>
48#include "BESInternalError.h"
50#include "DmrppNames.h"
52#define prolog std::string("AWSV4::").append(__func__).append("() - ")
57 const int SHA256_DIGEST_STRING_LENGTH = (SHA256_DIGEST_LENGTH << 1);
65 std::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;
78 std::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.length());
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};
95 static 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);
114 std::map<std::string,std::string> canonicalize_headers(
const std::vector<std::string>& headers) {
115 std::map<std::string,std::string> header_key2val;
116 for(
auto h = headers.begin(), end = headers.end(); h != end; ++h ) {
122 auto i = h->find(
':');
123 if (i == std::string::npos) {
124 header_key2val.clear();
125 return header_key2val;
128 std::string key = trim(h->substr(0, i));
129 const std::string val = trim(h->substr(i+1));
130 if (key.empty() || val.empty()) {
131 header_key2val.clear();
132 return header_key2val;
135 std::transform(key.begin(), key.end(), key.begin(),::tolower);
136 header_key2val[key] = val;
138 return header_key2val;
142 const std::string map_headers_string(
const std::map<std::string,std::string>& header_key2val) {
143 const std::string pair_delim{
":"};
145 for (
auto kv = header_key2val.begin(), end = header_key2val.end(); kv != end; ++kv) {
146 h.append(kv->first + pair_delim + kv->second + ENDL);
152 const std::string map_signed_headers(
const std::map<std::string,std::string>& header_key2val) {
153 const std::string signed_headers_delim{
";"};
154 std::vector<std::string> ks;
156 for (
auto kv = header_key2val.begin(), end = header_key2val.end(); kv != end; ++kv) {
157 ks.push_back(kv->first);
159 return join(ks,signed_headers_delim);
162 const std::string canonicalize_request(
const std::string& http_request_method,
163 const std::string& canonical_uri,
164 const std::string& canonical_query_string,
165 const std::string& canonical_headers,
166 const std::string& signed_headers,
167 const std::string& shar256_of_payload) {
168 return http_request_method + ENDL +
169 canonical_uri + ENDL +
170 canonical_query_string + ENDL +
171 canonical_headers + ENDL +
172 signed_headers + ENDL +
180 const std::string string_to_sign(
const std::string& algorithm,
181 const std::time_t& request_date,
182 const std::string& credential_scope,
183 const std::string& hashed_canonical_request) {
184 return algorithm + ENDL +
185 ISO8601_date(request_date) + ENDL +
186 credential_scope + ENDL +
187 hashed_canonical_request;
190 const std::string credential_scope(
const std::time_t& request_date,
191 const std::string region,
192 const std::string service) {
193 const std::string s{
"/"};
194 return utc_yyyymmdd(request_date) + s + region + s + service + s + AWS4_REQUEST;
198 const std::string ISO8601_date(
const std::time_t& t) {
199 char buf[
sizeof "20111008T070709Z"];
200 std::strftime(buf,
sizeof buf,
"%Y%m%dT%H%M%SZ", std::gmtime(&t));
201 return std::string{buf};
205 const std::string utc_yyyymmdd(
const std::time_t& t) {
206 char buf[
sizeof "20111008"];
207 std::strftime(buf,
sizeof buf,
"%Y%m%d", std::gmtime(&t));
208 return std::string{buf};
212 const std::string hmac_to_string(
const unsigned char *hmac) {
214 char buf[SHA256_DIGEST_STRING_LENGTH + 1];
215 for (
int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
217 snprintf(buf + (i * 2), 3,
"%02x", hmac[i]);
219 buf[SHA256_DIGEST_STRING_LENGTH] = 0;
220 return std::string{buf};
235 const std::string calculate_signature(
const std::time_t& request_date,
236 const std::string secret,
237 const std::string region,
238 const std::string service,
239 const std::string string_to_sign) {
242 unsigned char md[EVP_MAX_MD_SIZE+1];
245 const std::string k1 = AWS4 + secret;
246 const std::string yyyymmdd = utc_yyyymmdd(request_date);
247 unsigned char* kDate = HMAC(EVP_sha256(), (
const void *)k1.c_str(), k1.length(),
248 (
const unsigned char *)yyyymmdd.c_str(), yyyymmdd.length(), md, &md_len);
250 throw BESInternalError(
"Could not compute AWS V4 requst signature." ,__FILE__, __LINE__);
253 BESDEBUG(CREDS, prolog <<
"kDate: " << hmac_to_string(kDate) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl );
255 unsigned char *kRegion = HMAC(EVP_sha256(), md, (
size_t)md_len,
256 (
const unsigned char*)region.c_str(), region.length(), md, &md_len);
258 throw BESInternalError(
"Could not compute AWS V4 requst signature." ,__FILE__, __LINE__);
261 BESDEBUG(CREDS, prolog <<
"kRegion: " << hmac_to_string(kRegion) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl );
263 unsigned char *kService = HMAC(EVP_sha256(), md, (
size_t)md_len,
264 (
const unsigned char*)service.c_str(), service.length(), md, &md_len);
266 throw BESInternalError(
"Could not compute AWS V4 requst signature." ,__FILE__, __LINE__);
269 BESDEBUG(CREDS, prolog <<
"kService: " << hmac_to_string(kService) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl );
271 unsigned char *kSigning = HMAC(EVP_sha256(), md, (
size_t)md_len,
272 (
const unsigned char*)AWS4_REQUEST.c_str(), AWS4_REQUEST.length(), md, &md_len);
274 throw BESInternalError(
"Could not compute AWS V4 requst signature." ,__FILE__, __LINE__);
277 BESDEBUG(CREDS, prolog <<
"kSigning: " << hmac_to_string(kRegion) <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl );
279 unsigned char *kSig = HMAC(EVP_sha256(), md, (
size_t)md_len,
280 (
const unsigned char*)string_to_sign.c_str(), string_to_sign.length(), md, &md_len);
282 throw BESInternalError(
"Could not compute AWS V4 requst signature." ,__FILE__, __LINE__);
285 auto sig = hmac_to_string(md);
286 BESDEBUG(CREDS, prolog <<
"kSig: " << sig <<
" md_len: " << md_len <<
" md: " << hmac_to_string(md) << std::endl );
301 const std::string compute_awsv4_signature(
302 std::shared_ptr<http::url> &uri,
303 const std::time_t &request_date,
304 const std::string &public_key,
305 const std::string &secret_key,
306 const std::string ®ion,
307 const std::string &service) {
311 const auto canonical_uri = uri->path();
313 const auto canonical_query = uri->query();
317 const std::string sha256_empty_payload = {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"};
325 std::vector<std::string> headers{
"host: ",
"x-amz-date: "};
326 headers[0].append(uri->host());
327 headers[1].append(ISO8601_date(request_date));
329 const auto canonical_headers_map = canonicalize_headers(headers);
330 if (canonical_headers_map.empty()) {
331 throw std::runtime_error(
"Empty header list while building AWS V4 request signature");
333 const auto headers_string = map_headers_string(canonical_headers_map);
334 const auto signed_headers = map_signed_headers(canonical_headers_map);
335 const auto canonical_request = canonicalize_request(AWSV4::GET,
340 sha256_empty_payload);
342 BESDEBUG(CREDS, prolog <<
"Canonical Request: " << canonical_request << std::endl );
344 auto hashed_canonical_request = sha256_base16(canonical_request);
345 auto credential_scope = AWSV4::credential_scope(request_date,region,service);
346 auto string_to_sign = AWSV4::string_to_sign(STRING_TO_SIGN_ALGO,
349 hashed_canonical_request);
351 BESDEBUG(CREDS, prolog <<
"String to Sign: " << string_to_sign << std::endl );
353 auto signature = calculate_signature(request_date,
359 BESDEBUG(CREDS, prolog <<
"signature: " << signature << std::endl );
361 const std::string authorization_header = STRING_TO_SIGN_ALGO +
" Credential=" + public_key +
"/"
362 + credential_scope +
", SignedHeaders=" + signed_headers +
", Signature=" + signature;
364 BESDEBUG(CREDS, prolog <<
"authorization_header: " << authorization_header << std::endl );
366 return authorization_header;
exception thrown if internal error encountered