Imported Upstream version 1.34.0
[platform/upstream/grpc.git] / src / core / lib / security / credentials / external / aws_request_signer.cc
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 <grpc/support/port_platform.h>
17
18 #include "src/core/lib/security/credentials/external/aws_request_signer.h"
19
20 #include "absl/strings/ascii.h"
21 #include "absl/strings/escaping.h"
22 #include "absl/strings/str_format.h"
23 #include "absl/strings/str_join.h"
24 #include "absl/strings/str_split.h"
25 #include "absl/time/clock.h"
26 #include "absl/time/time.h"
27
28 #include <openssl/hmac.h>
29 #include <openssl/sha.h>
30
31 namespace grpc_core {
32
33 namespace {
34
35 const char kAlgorithm[] = "AWS4-HMAC-SHA256";
36 const char kDateFormat[] = "%a, %d %b %E4Y %H:%M:%S %Z";
37 const char kXAmzDateFormat[] = "%Y%m%dT%H%M%SZ";
38
39 void SHA256(const std::string& str, unsigned char out[SHA256_DIGEST_LENGTH]) {
40   SHA256_CTX sha256;
41   SHA256_Init(&sha256);
42   SHA256_Update(&sha256, str.c_str(), str.size());
43   SHA256_Final(out, &sha256);
44 }
45
46 std::string SHA256Hex(const std::string& str) {
47   unsigned char hash[SHA256_DIGEST_LENGTH];
48   SHA256(str, hash);
49   std::string hash_str(reinterpret_cast<char const*>(hash),
50                        SHA256_DIGEST_LENGTH);
51   return absl::BytesToHexString(hash_str);
52 }
53
54 std::string HMAC(const std::string& key, const std::string& msg) {
55   unsigned int len;
56   unsigned char digest[EVP_MAX_MD_SIZE];
57   HMAC(EVP_sha256(), key.c_str(), key.length(),
58        (const unsigned char*)msg.c_str(), msg.length(), digest, &len);
59   return std::string(digest, digest + len);
60 }
61
62 }  // namespace
63
64 AwsRequestSigner::AwsRequestSigner(
65     std::string access_key_id, std::string secret_access_key, std::string token,
66     std::string method, std::string url, std::string region,
67     std::string request_payload,
68     std::map<std::string, std::string> additional_headers, grpc_error** error)
69     : access_key_id_(std::move(access_key_id)),
70       secret_access_key_(std::move(secret_access_key)),
71       token_(std::move(token)),
72       method_(std::move(method)),
73       region_(std::move(region)),
74       request_payload_(std::move(request_payload)),
75       additional_headers_(std::move(additional_headers)) {
76   auto amz_date_it = additional_headers_.find("x-amz-date");
77   auto date_it = additional_headers_.find("date");
78   if (amz_date_it != additional_headers_.end() &&
79       date_it != additional_headers_.end()) {
80     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
81         "Only one of {date, x-amz-date} can be specified, not both.");
82     return;
83   }
84   if (amz_date_it != additional_headers_.end()) {
85     static_request_date_ = amz_date_it->second;
86   } else if (date_it != additional_headers_.end()) {
87     absl::Time request_date;
88     std::string err_str;
89     if (!absl::ParseTime(kDateFormat, date_it->second, &request_date,
90                          &err_str)) {
91       *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(err_str.c_str());
92       return;
93     }
94     static_request_date_ =
95         absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
96   }
97   url_ = grpc_uri_parse(url, false);
98   if (url_ == nullptr) {
99     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid Aws request url.");
100     return;
101   }
102 }
103
104 AwsRequestSigner::~AwsRequestSigner() { grpc_uri_destroy(url_); }
105
106 std::map<std::string, std::string> AwsRequestSigner::GetSignedRequestHeaders() {
107   std::string request_date_full;
108   if (!static_request_date_.empty()) {
109     if (!request_headers_.empty()) {
110       return request_headers_;
111     }
112     request_date_full = static_request_date_;
113   } else {
114     absl::Time request_date = absl::Now();
115     request_date_full =
116         absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
117   }
118   std::string request_date_short = request_date_full.substr(0, 8);
119   // TASK 1: Create a canonical request for Signature Version 4
120   // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
121   std::vector<absl::string_view> canonical_request_vector;
122   // 1. HTTPRequestMethod
123   canonical_request_vector.emplace_back(method_);
124   canonical_request_vector.emplace_back("\n");
125   // 2. CanonicalURI
126
127   canonical_request_vector.emplace_back(*url_->path == '\0' ? "/" : url_->path);
128   canonical_request_vector.emplace_back("\n");
129   // 3. CanonicalQueryString
130   canonical_request_vector.emplace_back(url_->query);
131   canonical_request_vector.emplace_back("\n");
132   // 4. CanonicalHeaders
133   if (request_headers_.empty()) {
134     request_headers_.insert({"host", url_->authority});
135     if (!token_.empty()) {
136       request_headers_.insert({"x-amz-security-token", token_});
137     }
138     for (const auto& header : additional_headers_) {
139       request_headers_.insert(
140           {absl::AsciiStrToLower(header.first), header.second});
141     }
142   }
143   if (additional_headers_.find("date") == additional_headers_.end()) {
144     request_headers_["x-amz-date"] = request_date_full;
145   }
146   std::vector<absl::string_view> canonical_headers_vector;
147   for (const auto& header : request_headers_) {
148     canonical_headers_vector.emplace_back(header.first);
149     canonical_headers_vector.emplace_back(":");
150     canonical_headers_vector.emplace_back(header.second);
151     canonical_headers_vector.emplace_back("\n");
152   }
153   std::string canonical_headers = absl::StrJoin(canonical_headers_vector, "");
154   canonical_request_vector.emplace_back(canonical_headers);
155   canonical_request_vector.emplace_back("\n");
156   // 5. SignedHeaders
157   std::vector<absl::string_view> signed_headers_vector;
158   for (const auto& header : request_headers_) {
159     signed_headers_vector.emplace_back(header.first);
160   }
161   std::string signed_headers = absl::StrJoin(signed_headers_vector, ";");
162   canonical_request_vector.emplace_back(signed_headers);
163   canonical_request_vector.emplace_back("\n");
164   // 6. RequestPayload
165   std::string hashed_request_payload = SHA256Hex(request_payload_);
166   canonical_request_vector.emplace_back(hashed_request_payload);
167   std::string canonical_request = absl::StrJoin(canonical_request_vector, "");
168   // TASK 2: Create a string to sign for Signature Version 4
169   // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
170   std::vector<absl::string_view> string_to_sign_vector;
171   // 1. Algorithm
172   string_to_sign_vector.emplace_back("AWS4-HMAC-SHA256");
173   string_to_sign_vector.emplace_back("\n");
174   // 2. RequestDateTime
175   string_to_sign_vector.emplace_back(request_date_full);
176   string_to_sign_vector.emplace_back("\n");
177   // 3. CredentialScope
178   std::pair<absl::string_view, absl::string_view> host_parts =
179       absl::StrSplit(url_->authority, absl::MaxSplits('.', 1));
180   std::string service_name(host_parts.first);
181   std::string credential_scope = absl::StrFormat(
182       "%s/%s/%s/aws4_request", request_date_short, region_, service_name);
183   string_to_sign_vector.emplace_back(credential_scope);
184   string_to_sign_vector.emplace_back("\n");
185   // 4. HashedCanonicalRequest
186   std::string hashed_canonical_request = SHA256Hex(canonical_request);
187   string_to_sign_vector.emplace_back(hashed_canonical_request);
188   std::string string_to_sign = absl::StrJoin(string_to_sign_vector, "");
189   // TASK 3: Task 3: Calculate the signature for AWS Signature Version 4
190   // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
191   // 1. Derive your signing key.
192   std::string date = HMAC("AWS4" + secret_access_key_, request_date_short);
193   std::string region = HMAC(date, region_);
194   std::string service = HMAC(region, service_name);
195   std::string signing = HMAC(service, "aws4_request");
196   // 2. Calculate the signature.
197   std::string signature_str = HMAC(signing, string_to_sign);
198   std::string signature = absl::BytesToHexString(signature_str);
199   // TASK 4: Add the signature to the HTTP request
200   // https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
201   std::string authorization_header = absl::StrFormat(
202       "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", kAlgorithm,
203       access_key_id_, credential_scope, signed_headers, signature);
204   request_headers_["Authorization"] = authorization_header;
205   return request_headers_;
206 }
207
208 }  // namespace grpc_core