• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2020 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 #include "src/core/lib/security/credentials/external/aws_request_signer.h"
17 
18 #include <grpc/support/port_platform.h>
19 #include <openssl/crypto.h>
20 #include <openssl/evp.h>
21 #include <openssl/hmac.h>
22 #include <openssl/sha.h>
23 
24 #include <utility>
25 #include <vector>
26 
27 #include "absl/status/statusor.h"
28 #include "absl/strings/ascii.h"
29 #include "absl/strings/escaping.h"
30 #include "absl/strings/str_cat.h"
31 #include "absl/strings/str_format.h"
32 #include "absl/strings/str_join.h"
33 #include "absl/strings/str_split.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/time/clock.h"
36 #include "absl/time/time.h"
37 
38 namespace grpc_core {
39 
40 namespace {
41 
42 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
43 const char kSha256[] = "SHA256";
44 #endif
45 const char kAlgorithm[] = "AWS4-HMAC-SHA256";
46 const char kDateFormat[] = "%a, %d %b %E4Y %H:%M:%S %Z";
47 const char kXAmzDateFormat[] = "%Y%m%dT%H%M%SZ";
48 
SHA256(const std::string & str,unsigned char out[SHA256_DIGEST_LENGTH])49 void SHA256(const std::string& str, unsigned char out[SHA256_DIGEST_LENGTH]) {
50 #if OPENSSL_VERSION_NUMBER < 0x30000000L
51   SHA256_CTX sha256;
52   SHA256_Init(&sha256);
53   SHA256_Update(&sha256, str.c_str(), str.size());
54   SHA256_Final(out, &sha256);
55 #else
56   EVP_Q_digest(nullptr, kSha256, nullptr, str.c_str(), str.size(), out,
57                nullptr);
58 #endif
59 }
60 
SHA256Hex(const std::string & str)61 std::string SHA256Hex(const std::string& str) {
62   unsigned char hash[SHA256_DIGEST_LENGTH];
63   SHA256(str, hash);
64   std::string hash_str(reinterpret_cast<char const*>(hash),
65                        SHA256_DIGEST_LENGTH);
66   return absl::BytesToHexString(hash_str);
67 }
68 
HMAC(const std::string & key,const std::string & msg)69 std::string HMAC(const std::string& key, const std::string& msg) {
70   unsigned int len;
71   unsigned char digest[EVP_MAX_MD_SIZE];
72   HMAC(EVP_sha256(), key.c_str(), key.length(),
73        reinterpret_cast<const unsigned char*>(msg.c_str()), msg.length(),
74        digest, &len);
75   return std::string(digest, digest + len);
76 }
77 
78 }  // namespace
79 
AwsRequestSigner(std::string access_key_id,std::string secret_access_key,std::string token,std::string method,std::string url,std::string region,std::string request_payload,std::map<std::string,std::string> additional_headers,grpc_error_handle * error)80 AwsRequestSigner::AwsRequestSigner(
81     std::string access_key_id, std::string secret_access_key, std::string token,
82     std::string method, std::string url, std::string region,
83     std::string request_payload,
84     std::map<std::string, std::string> additional_headers,
85     grpc_error_handle* error)
86     : access_key_id_(std::move(access_key_id)),
87       secret_access_key_(std::move(secret_access_key)),
88       token_(std::move(token)),
89       method_(std::move(method)),
90       region_(std::move(region)),
91       request_payload_(std::move(request_payload)),
92       additional_headers_(std::move(additional_headers)) {
93   auto amz_date_it = additional_headers_.find("x-amz-date");
94   auto date_it = additional_headers_.find("date");
95   if (amz_date_it != additional_headers_.end() &&
96       date_it != additional_headers_.end()) {
97     *error = GRPC_ERROR_CREATE(
98         "Only one of {date, x-amz-date} can be specified, not both.");
99     return;
100   }
101   if (amz_date_it != additional_headers_.end()) {
102     static_request_date_ = amz_date_it->second;
103   } else if (date_it != additional_headers_.end()) {
104     absl::Time request_date;
105     std::string err_str;
106     if (!absl::ParseTime(kDateFormat, date_it->second, &request_date,
107                          &err_str)) {
108       *error = GRPC_ERROR_CREATE(err_str.c_str());
109       return;
110     }
111     static_request_date_ =
112         absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
113   }
114   absl::StatusOr<URI> tmp_url = URI::Parse(url);
115   if (!tmp_url.ok()) {
116     *error = GRPC_ERROR_CREATE("Invalid Aws request url.");
117     return;
118   }
119   url_ = tmp_url.value();
120 }
121 
GetSignedRequestHeaders()122 std::map<std::string, std::string> AwsRequestSigner::GetSignedRequestHeaders() {
123   std::string request_date_full;
124   if (!static_request_date_.empty()) {
125     if (!request_headers_.empty()) {
126       return request_headers_;
127     }
128     request_date_full = static_request_date_;
129   } else {
130     absl::Time request_date = absl::Now();
131     request_date_full =
132         absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
133   }
134   std::string request_date_short = request_date_full.substr(0, 8);
135   // TASK 1: Create a canonical request for Signature Version 4
136   // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
137   std::vector<absl::string_view> canonical_request_vector;
138   // 1. HTTPRequestMethod
139   canonical_request_vector.emplace_back(method_);
140   canonical_request_vector.emplace_back("\n");
141   // 2. CanonicalURI
142   canonical_request_vector.emplace_back(
143       url_.path().empty() ? "/" : absl::string_view(url_.path()));
144   canonical_request_vector.emplace_back("\n");
145   // 3. CanonicalQueryString
146   std::vector<std::string> query_vector;
147   for (const URI::QueryParam& query_kv : url_.query_parameter_pairs()) {
148     query_vector.emplace_back(absl::StrCat(query_kv.key, "=", query_kv.value));
149   }
150   std::string query = absl::StrJoin(query_vector, "&");
151   canonical_request_vector.emplace_back(query);
152   canonical_request_vector.emplace_back("\n");
153   // 4. CanonicalHeaders
154   if (request_headers_.empty()) {
155     request_headers_.insert({"host", url_.authority()});
156     if (!token_.empty()) {
157       request_headers_.insert({"x-amz-security-token", token_});
158     }
159     for (const auto& header : additional_headers_) {
160       request_headers_.insert(
161           {absl::AsciiStrToLower(header.first), header.second});
162     }
163   }
164   if (additional_headers_.find("date") == additional_headers_.end()) {
165     request_headers_["x-amz-date"] = request_date_full;
166   }
167   std::vector<absl::string_view> canonical_headers_vector;
168   for (const auto& header : request_headers_) {
169     canonical_headers_vector.emplace_back(header.first);
170     canonical_headers_vector.emplace_back(":");
171     canonical_headers_vector.emplace_back(header.second);
172     canonical_headers_vector.emplace_back("\n");
173   }
174   std::string canonical_headers = absl::StrJoin(canonical_headers_vector, "");
175   canonical_request_vector.emplace_back(canonical_headers);
176   canonical_request_vector.emplace_back("\n");
177   // 5. SignedHeaders
178   std::vector<absl::string_view> signed_headers_vector;
179   signed_headers_vector.reserve(request_headers_.size());
180   for (const auto& header : request_headers_) {
181     signed_headers_vector.emplace_back(header.first);
182   }
183   std::string signed_headers = absl::StrJoin(signed_headers_vector, ";");
184   canonical_request_vector.emplace_back(signed_headers);
185   canonical_request_vector.emplace_back("\n");
186   // 6. RequestPayload
187   std::string hashed_request_payload = SHA256Hex(request_payload_);
188   canonical_request_vector.emplace_back(hashed_request_payload);
189   std::string canonical_request = absl::StrJoin(canonical_request_vector, "");
190   // TASK 2: Create a string to sign for Signature Version 4
191   // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
192   std::vector<absl::string_view> string_to_sign_vector;
193   // 1. Algorithm
194   string_to_sign_vector.emplace_back("AWS4-HMAC-SHA256");
195   string_to_sign_vector.emplace_back("\n");
196   // 2. RequestDateTime
197   string_to_sign_vector.emplace_back(request_date_full);
198   string_to_sign_vector.emplace_back("\n");
199   // 3. CredentialScope
200   std::pair<absl::string_view, absl::string_view> host_parts =
201       absl::StrSplit(url_.authority(), absl::MaxSplits('.', 1));
202   std::string service_name(host_parts.first);
203   std::string credential_scope = absl::StrFormat(
204       "%s/%s/%s/aws4_request", request_date_short, region_, service_name);
205   string_to_sign_vector.emplace_back(credential_scope);
206   string_to_sign_vector.emplace_back("\n");
207   // 4. HashedCanonicalRequest
208   std::string hashed_canonical_request = SHA256Hex(canonical_request);
209   string_to_sign_vector.emplace_back(hashed_canonical_request);
210   std::string string_to_sign = absl::StrJoin(string_to_sign_vector, "");
211   // TASK 3: Task 3: Calculate the signature for AWS Signature Version 4
212   // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
213   // 1. Derive your signing key.
214   std::string date = HMAC("AWS4" + secret_access_key_, request_date_short);
215   std::string region = HMAC(date, region_);
216   std::string service = HMAC(region, service_name);
217   std::string signing = HMAC(service, "aws4_request");
218   // 2. Calculate the signature.
219   std::string signature_str = HMAC(signing, string_to_sign);
220   std::string signature = absl::BytesToHexString(signature_str);
221   // TASK 4: Add the signature to the HTTP request
222   // https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
223   std::string authorization_header = absl::StrFormat(
224       "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", kAlgorithm,
225       access_key_id_, credential_scope, signed_headers, signature);
226   request_headers_["Authorization"] = authorization_header;
227   return request_headers_;
228 }
229 
230 }  // namespace grpc_core
231