Skip to content

Commit 293fc36

Browse files
committed
Fix bad signature for PutObject, and subsequent GetObject after it
We were not setting up correclty the Payload x-amz header, we now do. We also add a custom sorting function for our std::map of headers so that it always sorts them alphabetically.
1 parent 35d8979 commit 293fc36

8 files changed

Lines changed: 174 additions & 90 deletions

File tree

src/s3cpp/auth.cpp

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ void AWSSigV4Signer::sign(HttpRequestBase<T>& request) {
1717
// Autorization
1818
const std::string hash_algo = "AWS4-HMAC-SHA256";
1919

20+
// Compute payload hash and set header ONLY for body requests
21+
std::string payload_hash;
22+
if constexpr (std::is_same_v<T, HttpBodyRequest>) {
23+
const std::string& body = static_cast<HttpBodyRequest&>(request).getBody();
24+
payload_hash = hex(sha256(body));
25+
request.header("x-amz-content-sha256", payload_hash);
26+
} else {
27+
payload_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
28+
}
29+
2030
// Compute date and add it as a header
2131
const std::string timestamp = this->getTimestamp();
2232
request.header("X-Amz-Date", timestamp);
@@ -39,7 +49,7 @@ void AWSSigV4Signer::sign(HttpRequestBase<T>& request) {
3949
}
4050

4151
// Cannonical request
42-
std::string hex_cannonical_request = hex(sha256(createCannonicalRequest(request)));
52+
std::string hex_cannonical_request = hex(sha256(createCannonicalRequest(request, payload_hash)));
4353

4454
// To sign
4555
std::string string_to_sign = std::format("{}\n{}\n{}\n{}", hash_algo, timestamp, credential_scope, hex_cannonical_request);
@@ -50,7 +60,7 @@ void AWSSigV4Signer::sign(HttpRequestBase<T>& request) {
5060
}
5161

5262
template <typename T>
53-
std::string AWSSigV4Signer::createCannonicalRequest(HttpRequestBase<T>& request) {
63+
std::string AWSSigV4Signer::createCannonicalRequest(HttpRequestBase<T>& request, const std::string& payload_hash) {
5464
const std::string http_verb = request.getHttpMethodStr(request.getHttpMethod());
5565
std::string url = request.getURL();
5666

@@ -93,34 +103,29 @@ std::string AWSSigV4Signer::createCannonicalRequest(HttpRequestBase<T>& request)
93103
}
94104

95105
// Canonical Headers + SignedHeaders
96-
const std::map<std::string, std::string> headers = request.getHeaders();
106+
const std::map<std::string, std::string, LowerCaseCompare> headers = request.getHeaders();
97107
std::string cheaders = "";
98108
std::string signed_headers = "";
99109
uint_fast16_t i = 0;
100110
for (const auto& header : headers) {
101111
std::string kHeader = header.first;
102-
std::transform(kHeader.begin(), kHeader.end(), kHeader.begin(),
103-
::tolower);
112+
std::transform(kHeader.begin(), kHeader.end(), kHeader.begin(), ::tolower);
104113
if (i > 0)
105114
signed_headers += ";";
106115
signed_headers += kHeader;
107116
cheaders += kHeader + ":" + header.second + "\n";
108117
i++;
109118
}
110119

111-
std::string payload_hash;
112-
if constexpr (std::is_same_v<T, HttpBodyRequest>) {
113-
const std::string& body = static_cast<HttpBodyRequest&>(request).getBody();
114-
payload_hash = hex(sha256(body));
115-
} else {
116-
payload_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
117-
}
118-
119-
// The x-amz-content-sha256 header is required for Amazon S3 AWS requests. It provides a hash of the request payload. If there is no payload, you must provide the hash of an empty string.
120-
request.header("x-amz-content-sha256", payload_hash);
120+
std::string canonical_request = std::format("{}\n{}\n{}\n{}\n{}\n{}",
121+
http_verb,
122+
cannonical_uri,
123+
query_str,
124+
cheaders,
125+
signed_headers,
126+
payload_hash);
121127

122-
return std::format("{}\n{}\n{}\n{}\n{}\n{}", http_verb, cannonical_uri, query_str,
123-
cheaders, signed_headers, payload_hash);
128+
return canonical_request;
124129
}
125130

126131
const unsigned char* AWSSigV4Signer::sha256(const std::string& str) {
@@ -194,5 +199,5 @@ const unsigned char* AWSSigV4Signer::deriveSigningKey(const std::string request_
194199
// Why are we still here? Just to suffer?
195200
template void AWSSigV4Signer::sign<HttpRequest>(HttpRequestBase<HttpRequest>&);
196201
template void AWSSigV4Signer::sign<HttpBodyRequest>(HttpRequestBase<HttpBodyRequest>&);
197-
template std::string AWSSigV4Signer::createCannonicalRequest<HttpRequest>(HttpRequestBase<HttpRequest>&);
198-
template std::string AWSSigV4Signer::createCannonicalRequest<HttpBodyRequest>(HttpRequestBase<HttpBodyRequest>&);
202+
template std::string AWSSigV4Signer::createCannonicalRequest<HttpRequest>(HttpRequestBase<HttpRequest>&, const std::string&);
203+
template std::string AWSSigV4Signer::createCannonicalRequest<HttpBodyRequest>(HttpRequestBase<HttpBodyRequest>&, const std::string&);

src/s3cpp/auth.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class AWSSigV4Signer {
2020
void sign(HttpRequestBase<T>& request);
2121

2222
template <typename T>
23-
std::string createCannonicalRequest(HttpRequestBase<T>& request);
23+
std::string createCannonicalRequest(HttpRequestBase<T>& request, const std::string& payload_hash = "");
2424

2525
const unsigned char* sha256(const std::string& str);
2626
const unsigned char* HMAC_SHA256(const unsigned char* key, size_t key_len, const std::string& data);

src/s3cpp/httpclient.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ HttpResponse HttpClient::execute_get(HttpRequest& request) {
3838
"cURL handle is invalid");
3939
}
4040
std::string body_buf;
41-
std::map<std::string, std::string> headers_buf;
41+
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
4242
std::string error_buf;
4343

4444
// TODO(cristian): from libcurl docs, they state that each curl handle has
@@ -48,6 +48,9 @@ HttpResponse HttpClient::execute_get(HttpRequest& request) {
4848
//
4949
// curl_easy_reset(curl_handle);
5050

51+
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
52+
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, NULL);
53+
curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 0L);
5154
curl_easy_setopt(curl_handle, CURLOPT_URL, request.getURL().c_str());
5255
// body callback
5356
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback);
@@ -90,9 +93,10 @@ HttpResponse HttpClient::execute_head(HttpRequest& request) {
9093
// is invalidated in the HTTPClient copy constructor
9194
"cURL handle is invalid");
9295
}
93-
std::map<std::string, std::string> headers_buf;
96+
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
9497
std::string error_buf;
9598

99+
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, NULL);
96100
curl_easy_setopt(curl_handle, CURLOPT_URL, request.getURL().c_str());
97101
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, header_callback);
98102
curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, &headers_buf);
@@ -132,7 +136,7 @@ HttpResponse HttpClient::execute_post(HttpBodyRequest& request) {
132136
"cURL handle is invalid");
133137
}
134138
std::string body_buf;
135-
std::map<std::string, std::string> headers_buf;
139+
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
136140
std::string error_buf;
137141

138142
// TODO(cristian): from libcurl docs, they state that each curl handle has
@@ -142,6 +146,7 @@ HttpResponse HttpClient::execute_post(HttpBodyRequest& request) {
142146
//
143147
// curl_easy_reset(curl_handle);
144148

149+
curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 0L);
145150
curl_easy_setopt(curl_handle, CURLOPT_URL, request.getURL().c_str());
146151
// body callback
147152
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback);
@@ -193,7 +198,7 @@ HttpResponse HttpClient::execute_delete(HttpBodyRequest& request) {
193198
"cURL handle is invalid");
194199
}
195200
std::string body_buf;
196-
std::map<std::string, std::string> headers_buf;
201+
std::map<std::string, std::string, LowerCaseCompare> headers_buf;
197202
std::string error_buf;
198203

199204
// TODO(cristian): from libcurl docs, they state that each curl handle has

src/s3cpp/httpclient.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,35 @@ enum class HttpMethod {
2121
Delete
2222
};
2323

24+
struct LowerCaseCompare { // A custom lambda to sort keys alphabetically
25+
bool operator()(const std::string& a, const std::string& b) const {
26+
std::string sa = a;
27+
std::string sb = b;
28+
std::transform(sa.begin(), sa.end(), sa.begin(), ::tolower);
29+
std::transform(sb.begin(), sb.end(), sb.begin(), ::tolower);
30+
return sa < sb;
31+
}
32+
};
33+
2434
class HttpResponse {
2535
public:
2636
HttpResponse(int c)
2737
: code_(c) { };
2838
HttpResponse(int c, std::string b)
2939
: code_(c)
3040
, body_(std::move(b)) { };
31-
HttpResponse(int c, std::map<std::string, std::string> h)
41+
HttpResponse(int c, std::map<std::string, std::string, LowerCaseCompare> h)
3242
: code_(c)
3343
, headers_(std::move(h)) { };
34-
HttpResponse(int c, std::string b, std::map<std::string, std::string> h)
44+
HttpResponse(int c, std::string b, std::map<std::string, std::string, LowerCaseCompare> h)
3545
: code_(c)
3646
, body_(std::move(b))
3747
, headers_(std::move(h)) { };
3848

3949
// Getters
4050
int status() const { return code_; }
4151
const std::string& body() const { return body_; }
42-
const std::map<std::string, std::string>& headers() const { return headers_; }
52+
const auto& headers() const { return headers_; }
4353

4454
// Status via code
4555
bool is_ok() const { return code_ >= 200 && code_ < 300; }
@@ -54,7 +64,7 @@ class HttpResponse {
5464
private:
5565
int code_;
5666
std::string body_;
57-
std::map<std::string, std::string> headers_;
67+
std::map<std::string, std::string, LowerCaseCompare> headers_;
5868
};
5969

6070
// HttpRequest will handle all the headers and request params
@@ -93,11 +103,11 @@ class HttpRequestBase {
93103
const std::string& getURL() const { return URL_; }
94104
const HttpMethod& getHttpMethod() const { return http_method_; }
95105
const long long getTimeout() const { return timeout_.count(); }
96-
const std::map<std::string, std::string>& getHeaders() const {
106+
const std::map<std::string, std::string, LowerCaseCompare>& getHeaders() const {
97107
return headers_;
98108
}
99109

100-
// Cannonicalize HTTP verb from the request
110+
// Cannonicalize HTTP verb from the request
101111
const std::string getHttpMethodStr(const HttpMethod& http_method) const {
102112
switch (http_method) {
103113
case HttpMethod::Get:
@@ -115,12 +125,10 @@ class HttpRequestBase {
115125
}
116126
}
117127

118-
119-
120128
protected:
121129
HttpClient& client_;
122130
std::string URL_;
123-
std::map<std::string, std::string> headers_;
131+
std::map<std::string, std::string, LowerCaseCompare> headers_;
124132
std::chrono::seconds timeout_;
125133
HttpMethod http_method_;
126134
};

0 commit comments

Comments
 (0)