aws.cc
1 // Copyright 2019, Beeri 15. All rights reserved.
2 // Author: Roman Gershman (romange@gmail.com)
3 //
4 #include "util/aws/aws.h"
5 
6 #include <openssl/hmac.h>
7 #include <openssl/sha.h>
8 #include <openssl/evp.h>
9 
10 #include "absl/strings/escaping.h"
11 #include "absl/strings/str_cat.h"
12 #include "absl/strings/str_join.h"
13 #include "absl/strings/str_split.h"
14 #include "base/logging.h"
15 
16 namespace util {
17 
18 using namespace std;
19 using namespace boost;
20 namespace h2 = beast::http;
21 
22 namespace {
23 
24 void HMAC(absl::string_view key, absl::string_view msg, char dest[SHA256_DIGEST_LENGTH]) {
25  HMAC_CTX* hmac = HMAC_CTX_new();
26 
27  CHECK_EQ(1, HMAC_CTX_reset(hmac));
28  CHECK_EQ(1, HMAC_Init_ex(hmac, reinterpret_cast<const uint8_t*>(key.data()), key.size(),
29  EVP_sha256(), NULL));
30 
31  CHECK_EQ(1, HMAC_Update(hmac, reinterpret_cast<const uint8_t*>(msg.data()), msg.size()));
32 
33  uint8_t* ptr = reinterpret_cast<uint8_t*>(dest);
34  unsigned len = 32;
35  CHECK_EQ(1, HMAC_Final(hmac, ptr, &len));
36  HMAC_CTX_free(hmac);
37  CHECK_EQ(len, 32);
38 }
39 
40 string GetSignatureKey(absl::string_view key, absl::string_view datestamp, absl::string_view region,
41  absl::string_view service) {
42  char sign[32];
43  absl::string_view sign_key{sign, sizeof(sign)};
44  HMAC(absl::StrCat("AWS4", key), datestamp, sign);
45  HMAC(sign_key, region, sign);
46  HMAC(sign_key, service, sign);
47  HMAC(sign_key, "aws4_request", sign);
48 
49  return string(sign_key);
50 }
51 
52 void Hexify(const char* str, size_t len, char* dest) {
53  static constexpr char kHex[] = "0123456789abcdef";
54 
55  for (unsigned i = 0; i < len; ++i) {
56  char c = str[i];
57  *dest++ = kHex[(c >> 4) & 0xF];
58  *dest++ = kHex[c & 0xF];
59  }
60  *dest = '\0';
61 }
62 
63 // TODO: those are used in gcs_utils as well.
64 using bb_str_view = ::boost::beast::string_view;
65 
66 inline absl::string_view absl_sv(const bb_str_view s) {
67  return absl::string_view{s.data(), s.size()};
68 }
69 
70 constexpr char kAlgo[] = "AWS4-HMAC-SHA256";
71 
72 } // namespace
73 
74 namespace detail {
75 
76 void Sha256String(absl::string_view str, char out[65]) {
77  unsigned char hash[SHA256_DIGEST_LENGTH];
78 
79 #if 0
80  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
81  CHECK(mdctx);
82 
83  CHECK_EQ(1, EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL));
84 
85  CHECK_EQ(1, EVP_DigestUpdate(mdctx, str.data(), str.size()));
86 
87  unsigned int len = 0;
88  CHECK_EQ(1, EVP_DigestFinal_ex(mdctx, hash, &len));
89  CHECK_EQ(SHA256_DIGEST_LENGTH, len);
90 
91  EVP_MD_CTX_free(mdctx);
92 
93 #else
94  SHA256_CTX sha256;
95  SHA256_Init(&sha256);
96  SHA256_Update(&sha256, str.data(), str.size());
97  SHA256_Final(hash, &sha256);
98 #endif
99 
100  Hexify(reinterpret_cast<const char*>(hash), SHA256_DIGEST_LENGTH, out);
101 }
102 
103 void Sha256String(const ::boost::beast::multi_buffer& mb, char out[65]) {
104  unsigned char hash[SHA256_DIGEST_LENGTH];
105  SHA256_CTX sha256;
106  SHA256_Init(&sha256);
107 
108  for (const auto& e: mb.cdata()) {
109  SHA256_Update(&sha256, e.data(), e.size());
110  }
111  SHA256_Final(hash, &sha256);
112  Hexify(reinterpret_cast<const char*>(hash), SHA256_DIGEST_LENGTH, out);
113 }
114 
115 } // namespace detail
116 
117 const char AWS::kHashEmpty[] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
118 
119 ::boost::asio::ssl::context AWS::CheckedSslContext() {
120  system::error_code ec;
121  asio::ssl::context cntx{asio::ssl::context::tlsv12_client};
122  cntx.set_options(boost::asio::ssl::context::default_workarounds |
123  boost::asio::ssl::context::no_compression | boost::asio::ssl::context::no_sslv2 |
124  boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 |
125  boost::asio::ssl::context::no_tlsv1_1);
126  cntx.set_verify_mode(asio::ssl::verify_peer, ec);
127  if (ec) {
128  LOG(FATAL) << "Could not set verify mode " << ec;
129  }
130  // cntx.add_certificate_authority(asio::buffer(cert_string.data(), cert_string.size()), ec);
131  cntx.load_verify_file("/etc/ssl/certs/ca-certificates.crt", ec);
132  if (ec) {
133  LOG(FATAL) << "Could not load certificates file" << ec;
134  }
135 
136  return cntx;
137 }
138 
139 Status AWS::Init() {
140  const char* access_key = getenv("AWS_ACCESS_KEY_ID");
141  const char* secret_key = getenv("AWS_SECRET_ACCESS_KEY");
142 
143  if (!access_key)
144  return Status("Can not find AWS_ACCESS_KEY_ID");
145 
146  if (!secret_key)
147  return Status("Can not find AWS_ACCESS_KEY_ID");
148 
149  secret_ = secret_key;
150  access_key_ = access_key;
151 
152  time_t now = time(nullptr); // Must be recent (upto 900sec skew is allowed vs amazon servers).
153 
154  struct tm tm_s;
155  CHECK(&tm_s == gmtime_r(&now, &tm_s));
156 
157  CHECK_GT(strftime(date_str_, arraysize(date_str_), "%Y%m%d", &tm_s), 0);
158  sign_key_ = GetSignatureKey(secret_, date_str_, region_id_, service_);
159 
160  credential_scope_ = absl::StrCat(date_str_, "/", region_id_, "/", service_, "/", "aws4_request");
161 
162  return Status::OK;
163 }
164 
165 // TODO: to support date refreshes - i.e. to update sign_key_, credential_scope_
166 // if the current has changed.
167 void AWS::Sign(absl::string_view domain, absl::string_view sha256,
168  ::boost::beast::http::header<true, ::boost::beast::http::fields>* header) const {
169  header->set(h2::field::host, domain);
170 
171  // We show consider to pass it via argument to make the function test-friendly.
172  time_t now = time(nullptr); // Must be recent (upto 900sec skew is allowed vs amazon servers).
173 
174  struct tm tm_s;
175  CHECK(&tm_s == gmtime_r(&now, &tm_s));
176 
177  char amz_date[32];
178 
179  CHECK_GT(strftime(amz_date, arraysize(amz_date), "%Y%m%dT%H%M00Z", &tm_s), 0);
180  VLOG(1) << "Time now: " << now;
181 
182  header->set("x-amz-date", amz_date);
183  header->set("x-amz-content-sha256", sha256);
184 
185  string canonical_headers = absl::StrCat("host", ":", domain, "\n");
186  absl::StrAppend(&canonical_headers, "x-amz-content-sha256", ":", sha256, "\n");
187  absl::StrAppend(&canonical_headers, "x-amz-date", ":", amz_date, "\n");
188 
189  string auth_header =
190  AuthHeader(absl_sv(header->method_string()), canonical_headers, absl_sv(header->target()),
191  sha256, amz_date);
192 
193  header->set(h2::field::authorization, auth_header);
194 }
195 
196 string AWS::AuthHeader(absl::string_view method, absl::string_view headers,
197  absl::string_view target, absl::string_view content_sha256,
198  absl::string_view amz_date) const {
199  size_t pos = target.find('?');
200  absl::string_view url = target.substr(0, pos);
201  absl::string_view query_string;
202  string canonical_querystring;
203 
204  if (pos != string::npos) {
205  query_string = target.substr(pos + 1);
206 
207  // We must sign query string with params in alphabetical order
208  vector<absl::string_view> params = absl::StrSplit(query_string, "&", absl::SkipWhitespace{});
209  sort(params.begin(), params.end());
210  canonical_querystring = absl::StrJoin(params, "&");
211  }
212 
213  string canonical_request = absl::StrCat(method, "\n", url, "\n", canonical_querystring, "\n");
214  string signed_headers = "host;x-amz-content-sha256;x-amz-date";
215  absl::StrAppend(&canonical_request, headers, "\n", signed_headers, "\n", content_sha256);
216  VLOG(1) << "CanonicalRequest:\n" << canonical_request << "\n-------------------\n";
217 
218  char hexdigest[65];
219  detail::Sha256String(canonical_request, hexdigest);
220 
221  string string_to_sign =
222  absl::StrCat(kAlgo, "\n", amz_date, "\n", credential_scope_, "\n", hexdigest);
223 
224  char signature[SHA256_DIGEST_LENGTH];
225  HMAC(sign_key_, string_to_sign, signature);
226  Hexify(signature, SHA256_DIGEST_LENGTH, hexdigest);
227 
228  string authorization_header =
229  absl::StrCat(kAlgo, " Credential=", access_key_, "/", credential_scope_,
230  ",SignedHeaders=", signed_headers, ",Signature=", hexdigest);
231 
232  return authorization_header;
233 }
234 
235 } // namespace util