2 // Copyright 2020 gRPC authors.
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
8 // http://www.apache.org/licenses/LICENSE-2.0
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.
16 #include <grpc/support/port_platform.h>
18 #include "src/core/lib/security/credentials/external/aws_request_signer.h"
20 #include <openssl/hmac.h>
21 #include <openssl/sha.h>
23 #include "absl/strings/ascii.h"
24 #include "absl/strings/escaping.h"
25 #include "absl/strings/str_format.h"
26 #include "absl/strings/str_join.h"
27 #include "absl/strings/str_split.h"
28 #include "absl/time/clock.h"
29 #include "absl/time/time.h"
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";
39 void SHA256(const std::string& str, unsigned char out[SHA256_DIGEST_LENGTH]) {
42 SHA256_Update(&sha256, str.c_str(), str.size());
43 SHA256_Final(out, &sha256);
46 std::string SHA256Hex(const std::string& str) {
47 unsigned char hash[SHA256_DIGEST_LENGTH];
49 std::string hash_str(reinterpret_cast<char const*>(hash),
50 SHA256_DIGEST_LENGTH);
51 return absl::BytesToHexString(hash_str);
54 std::string HMAC(const std::string& key, const std::string& msg) {
56 unsigned char digest[EVP_MAX_MD_SIZE];
57 HMAC(EVP_sha256(), key.c_str(), key.length(),
58 reinterpret_cast<const unsigned char*>(msg.c_str()), msg.length(),
60 return std::string(digest, digest + len);
65 AwsRequestSigner::AwsRequestSigner(
66 std::string access_key_id, std::string secret_access_key, std::string token,
67 std::string method, std::string url, std::string region,
68 std::string request_payload,
69 std::map<std::string, std::string> additional_headers,
70 grpc_error_handle* error)
71 : access_key_id_(std::move(access_key_id)),
72 secret_access_key_(std::move(secret_access_key)),
73 token_(std::move(token)),
74 method_(std::move(method)),
75 region_(std::move(region)),
76 request_payload_(std::move(request_payload)),
77 additional_headers_(std::move(additional_headers)) {
78 auto amz_date_it = additional_headers_.find("x-amz-date");
79 auto date_it = additional_headers_.find("date");
80 if (amz_date_it != additional_headers_.end() &&
81 date_it != additional_headers_.end()) {
82 *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
83 "Only one of {date, x-amz-date} can be specified, not both.");
86 if (amz_date_it != additional_headers_.end()) {
87 static_request_date_ = amz_date_it->second;
88 } else if (date_it != additional_headers_.end()) {
89 absl::Time request_date;
91 if (!absl::ParseTime(kDateFormat, date_it->second, &request_date,
93 *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(err_str.c_str());
96 static_request_date_ =
97 absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
99 absl::StatusOr<URI> tmp_url = URI::Parse(url);
101 *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid Aws request url.");
104 url_ = tmp_url.value();
107 std::map<std::string, std::string> AwsRequestSigner::GetSignedRequestHeaders() {
108 std::string request_date_full;
109 if (!static_request_date_.empty()) {
110 if (!request_headers_.empty()) {
111 return request_headers_;
113 request_date_full = static_request_date_;
115 absl::Time request_date = absl::Now();
117 absl::FormatTime(kXAmzDateFormat, request_date, absl::UTCTimeZone());
119 std::string request_date_short = request_date_full.substr(0, 8);
120 // TASK 1: Create a canonical request for Signature Version 4
121 // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
122 std::vector<absl::string_view> canonical_request_vector;
123 // 1. HTTPRequestMethod
124 canonical_request_vector.emplace_back(method_);
125 canonical_request_vector.emplace_back("\n");
127 canonical_request_vector.emplace_back(
128 url_.path().empty() ? "/" : absl::string_view(url_.path()));
129 canonical_request_vector.emplace_back("\n");
130 // 3. CanonicalQueryString
131 std::vector<std::string> query_vector;
132 for (const URI::QueryParam& query_kv : url_.query_parameter_pairs()) {
133 query_vector.emplace_back(absl::StrCat(query_kv.key, "=", query_kv.value));
135 std::string query = absl::StrJoin(query_vector, "&");
136 canonical_request_vector.emplace_back(query);
137 canonical_request_vector.emplace_back("\n");
138 // 4. CanonicalHeaders
139 if (request_headers_.empty()) {
140 request_headers_.insert({"host", url_.authority()});
141 if (!token_.empty()) {
142 request_headers_.insert({"x-amz-security-token", token_});
144 for (const auto& header : additional_headers_) {
145 request_headers_.insert(
146 {absl::AsciiStrToLower(header.first), header.second});
149 if (additional_headers_.find("date") == additional_headers_.end()) {
150 request_headers_["x-amz-date"] = request_date_full;
152 std::vector<absl::string_view> canonical_headers_vector;
153 for (const auto& header : request_headers_) {
154 canonical_headers_vector.emplace_back(header.first);
155 canonical_headers_vector.emplace_back(":");
156 canonical_headers_vector.emplace_back(header.second);
157 canonical_headers_vector.emplace_back("\n");
159 std::string canonical_headers = absl::StrJoin(canonical_headers_vector, "");
160 canonical_request_vector.emplace_back(canonical_headers);
161 canonical_request_vector.emplace_back("\n");
163 std::vector<absl::string_view> signed_headers_vector;
164 for (const auto& header : request_headers_) {
165 signed_headers_vector.emplace_back(header.first);
167 std::string signed_headers = absl::StrJoin(signed_headers_vector, ";");
168 canonical_request_vector.emplace_back(signed_headers);
169 canonical_request_vector.emplace_back("\n");
171 std::string hashed_request_payload = SHA256Hex(request_payload_);
172 canonical_request_vector.emplace_back(hashed_request_payload);
173 std::string canonical_request = absl::StrJoin(canonical_request_vector, "");
174 // TASK 2: Create a string to sign for Signature Version 4
175 // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
176 std::vector<absl::string_view> string_to_sign_vector;
178 string_to_sign_vector.emplace_back("AWS4-HMAC-SHA256");
179 string_to_sign_vector.emplace_back("\n");
180 // 2. RequestDateTime
181 string_to_sign_vector.emplace_back(request_date_full);
182 string_to_sign_vector.emplace_back("\n");
183 // 3. CredentialScope
184 std::pair<absl::string_view, absl::string_view> host_parts =
185 absl::StrSplit(url_.authority(), absl::MaxSplits('.', 1));
186 std::string service_name(host_parts.first);
187 std::string credential_scope = absl::StrFormat(
188 "%s/%s/%s/aws4_request", request_date_short, region_, service_name);
189 string_to_sign_vector.emplace_back(credential_scope);
190 string_to_sign_vector.emplace_back("\n");
191 // 4. HashedCanonicalRequest
192 std::string hashed_canonical_request = SHA256Hex(canonical_request);
193 string_to_sign_vector.emplace_back(hashed_canonical_request);
194 std::string string_to_sign = absl::StrJoin(string_to_sign_vector, "");
195 // TASK 3: Task 3: Calculate the signature for AWS Signature Version 4
196 // https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
197 // 1. Derive your signing key.
198 std::string date = HMAC("AWS4" + secret_access_key_, request_date_short);
199 std::string region = HMAC(date, region_);
200 std::string service = HMAC(region, service_name);
201 std::string signing = HMAC(service, "aws4_request");
202 // 2. Calculate the signature.
203 std::string signature_str = HMAC(signing, string_to_sign);
204 std::string signature = absl::BytesToHexString(signature_str);
205 // TASK 4: Add the signature to the HTTP request
206 // https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
207 std::string authorization_header = absl::StrFormat(
208 "%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", kAlgorithm,
209 access_key_id_, credential_scope, signed_headers, signature);
210 request_headers_["Authorization"] = authorization_header;
211 return request_headers_;
214 } // namespace grpc_core