]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_auth_s3.cc
import quincy 17.2.0
[ceph.git] / ceph / src / rgw / rgw_auth_s3.cc
CommitLineData
7c673cae 1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
9f95a23c 2// vim: ts=8 sw=2 smarttab ft=cpp
7c673cae 3
31f18b77 4#include <algorithm>
7c673cae 5#include <map>
31f18b77 6#include <iterator>
7c673cae 7#include <string>
f67539c2 8#include <string_view>
31f18b77 9#include <vector>
7c673cae
FG
10
11#include "common/armor.h"
12#include "common/utf8.h"
11fdf7f2 13#include "rgw_rest_s3.h"
31f18b77 14#include "rgw_auth_s3.h"
7c673cae
FG
15#include "rgw_common.h"
16#include "rgw_client_io.h"
17#include "rgw_rest.h"
18#include "rgw_crypt_sanitize.h"
31f18b77
FG
19
20#include <boost/container/small_vector.hpp>
f67539c2 21#include <boost/algorithm/string.hpp>
28e407b8 22#include <boost/algorithm/string/trim_all.hpp>
31f18b77 23
7c673cae
FG
24#define dout_context g_ceph_context
25#define dout_subsys ceph_subsys_rgw
26
20effc67
TL
27using namespace std;
28
7c673cae
FG
29static const auto signed_subresources = {
30 "acl",
31 "cors",
32 "delete",
20effc67 33 "encryption",
7c673cae
FG
34 "lifecycle",
35 "location",
36 "logging",
37 "notification",
38 "partNumber",
39 "policy",
9f95a23c
TL
40 "policyStatus",
41 "publicAccessBlock",
7c673cae
FG
42 "requestPayment",
43 "response-cache-control",
44 "response-content-disposition",
45 "response-content-encoding",
46 "response-content-language",
47 "response-content-type",
48 "response-expires",
224ce89b 49 "tagging",
7c673cae
FG
50 "torrent",
51 "uploadId",
52 "uploads",
7c673cae
FG
53 "versionId",
54 "versioning",
55 "versions",
eafe8130
TL
56 "website",
57 "object-lock"
7c673cae
FG
58};
59
60/*
61 * ?get the canonical amazon-style header for something?
62 */
63
64static std::string
9f95a23c 65get_canon_amz_hdr(const meta_map_t& meta_map)
7c673cae
FG
66{
67 std::string dest;
68
69 for (const auto& kv : meta_map) {
70 dest.append(kv.first);
71 dest.append(":");
72 dest.append(kv.second);
73 dest.append("\n");
74 }
75
76 return dest;
77}
78
79/*
80 * ?get the canonical representation of the object's location
81 */
82static std::string
b3b6e05e 83get_canon_resource(const DoutPrefixProvider *dpp, const char* const request_uri,
7c673cae
FG
84 const std::map<std::string, std::string>& sub_resources)
85{
86 std::string dest;
87
88 if (request_uri) {
89 dest.append(request_uri);
90 }
91
92 bool initial = true;
93 for (const auto& subresource : signed_subresources) {
94 const auto iter = sub_resources.find(subresource);
95 if (iter == std::end(sub_resources)) {
96 continue;
97 }
98
99 if (initial) {
100 dest.append("?");
101 initial = false;
102 } else {
103 dest.append("&");
104 }
105
106 dest.append(iter->first);
107 if (! iter->second.empty()) {
108 dest.append("=");
109 dest.append(iter->second);
110 }
111 }
112
b3b6e05e 113 ldpp_dout(dpp, 10) << "get_canon_resource(): dest=" << dest << dendl;
7c673cae
FG
114 return dest;
115}
116
117/*
118 * get the header authentication information required to
119 * compute a request's signature
120 */
121void rgw_create_s3_canonical_header(
b3b6e05e 122 const DoutPrefixProvider *dpp,
7c673cae
FG
123 const char* const method,
124 const char* const content_md5,
125 const char* const content_type,
126 const char* const date,
9f95a23c
TL
127 const meta_map_t& meta_map,
128 const meta_map_t& qs_map,
7c673cae
FG
129 const char* const request_uri,
130 const std::map<std::string, std::string>& sub_resources,
131 std::string& dest_str)
132{
133 std::string dest;
134
135 if (method) {
136 dest = method;
137 }
138 dest.append("\n");
139
140 if (content_md5) {
141 dest.append(content_md5);
142 }
143 dest.append("\n");
144
145 if (content_type) {
146 dest.append(content_type);
147 }
148 dest.append("\n");
149
150 if (date) {
151 dest.append(date);
152 }
153 dest.append("\n");
154
155 dest.append(get_canon_amz_hdr(meta_map));
a8e16298 156 dest.append(get_canon_amz_hdr(qs_map));
b3b6e05e 157 dest.append(get_canon_resource(dpp, request_uri, sub_resources));
7c673cae
FG
158
159 dest_str = dest;
160}
161
7c673cae
FG
162static inline bool is_base64_for_content_md5(unsigned char c) {
163 return (isalnum(c) || isspace(c) || (c == '+') || (c == '/') || (c == '='));
164}
165
a8e16298 166static inline void get_v2_qs_map(const req_info& info,
9f95a23c 167 meta_map_t& qs_map) {
a8e16298
TL
168 const auto& params = const_cast<RGWHTTPArgs&>(info.args).get_params();
169 for (const auto& elt : params) {
170 std::string k = boost::algorithm::to_lower_copy(elt.first);
171 if (k.find("x-amz-meta-") == /* offset */ 0) {
9f95a23c 172 rgw_add_amz_meta_header(qs_map, k, elt.second);
a8e16298 173 }
f67539c2
TL
174 if (k == "x-amz-security-token") {
175 qs_map[k] = elt.second;
176 }
a8e16298
TL
177 }
178}
179
7c673cae
FG
180/*
181 * get the header authentication information required to
182 * compute a request's signature
183 */
b3b6e05e
TL
184bool rgw_create_s3_canonical_header(const DoutPrefixProvider *dpp,
185 const req_info& info,
7c673cae
FG
186 utime_t* const header_time,
187 std::string& dest,
188 const bool qsr)
189{
190 const char* const content_md5 = info.env->get("HTTP_CONTENT_MD5");
191 if (content_md5) {
192 for (const char *p = content_md5; *p; p++) {
193 if (!is_base64_for_content_md5(*p)) {
b3b6e05e 194 ldpp_dout(dpp, 0) << "NOTICE: bad content-md5 provided (not base64),"
7c673cae
FG
195 << " aborting request p=" << *p << " " << (int)*p << dendl;
196 return false;
197 }
198 }
199 }
200
201 const char *content_type = info.env->get("CONTENT_TYPE");
202
203 std::string date;
9f95a23c 204 meta_map_t qs_map;
a8e16298 205
7c673cae 206 if (qsr) {
a8e16298 207 get_v2_qs_map(info, qs_map); // handle qs metadata
7c673cae
FG
208 date = info.args.get("Expires");
209 } else {
224ce89b 210 const char *str = info.env->get("HTTP_X_AMZ_DATE");
7c673cae 211 const char *req_date = str;
224ce89b
WB
212 if (str == NULL) {
213 req_date = info.env->get("HTTP_DATE");
7c673cae 214 if (!req_date) {
b3b6e05e 215 ldpp_dout(dpp, 0) << "NOTICE: missing date for auth header" << dendl;
7c673cae
FG
216 return false;
217 }
224ce89b 218 date = req_date;
7c673cae
FG
219 }
220
221 if (header_time) {
222 struct tm t;
223 if (!parse_rfc2616(req_date, &t)) {
b3b6e05e 224 ldpp_dout(dpp, 0) << "NOTICE: failed to parse date for auth header" << dendl;
7c673cae
FG
225 return false;
226 }
227 if (t.tm_year < 70) {
b3b6e05e 228 ldpp_dout(dpp, 0) << "NOTICE: bad date (predates epoch): " << req_date << dendl;
7c673cae
FG
229 return false;
230 }
231 *header_time = utime_t(internal_timegm(&t), 0);
f67539c2 232 *header_time -= t.tm_gmtoff;
7c673cae
FG
233 }
234 }
235
236 const auto& meta_map = info.x_meta_map;
237 const auto& sub_resources = info.args.get_sub_resources();
238
239 std::string request_uri;
240 if (info.effective_uri.empty()) {
241 request_uri = info.request_uri;
242 } else {
243 request_uri = info.effective_uri;
244 }
245
b3b6e05e 246 rgw_create_s3_canonical_header(dpp, info.method, content_md5, content_type,
a8e16298
TL
247 date.c_str(), meta_map, qs_map,
248 request_uri.c_str(), sub_resources, dest);
7c673cae
FG
249 return true;
250}
251
31f18b77 252
9f95a23c 253namespace rgw::auth::s3 {
31f18b77 254
94b18763
FG
255bool is_time_skew_ok(time_t t)
256{
257 auto req_tp = ceph::coarse_real_clock::from_time_t(t);
258 auto cur_tp = ceph::coarse_real_clock::now();
259
11fdf7f2 260 if (std::chrono::abs(cur_tp - req_tp) > RGW_AUTH_GRACE) {
94b18763
FG
261 dout(10) << "NOTICE: request time skew too big." << dendl;
262 using ceph::operator<<;
263 dout(10) << "req_tp=" << req_tp << ", cur_tp=" << cur_tp << dendl;
264 return false;
265 }
266
267 return true;
268}
31f18b77
FG
269
270static inline int parse_v4_query_string(const req_info& info, /* in */
f67539c2
TL
271 std::string_view& credential, /* out */
272 std::string_view& signedheaders, /* out */
273 std::string_view& signature, /* out */
274 std::string_view& date, /* out */
275 std::string_view& sessiontoken) /* out */
7c673cae 276{
31f18b77 277 /* auth ships with req params ... */
7c673cae 278
31f18b77 279 /* look for required params */
f67539c2 280 credential = info.args.get("x-amz-credential");
31f18b77
FG
281 if (credential.size() == 0) {
282 return -EPERM;
283 }
7c673cae 284
f67539c2 285 date = info.args.get("x-amz-date");
31f18b77
FG
286 struct tm date_t;
287 if (!parse_iso8601(sview2cstr(date).data(), &date_t, nullptr, false)) {
288 return -EPERM;
7c673cae 289 }
7c673cae 290
f67539c2 291 std::string_view expires = info.args.get("x-amz-expires");
94b18763
FG
292 if (expires.empty()) {
293 return -EPERM;
7c673cae 294 }
94b18763
FG
295 /* X-Amz-Expires provides the time period, in seconds, for which
296 the generated presigned URL is valid. The minimum value
297 you can set is 1, and the maximum is 604800 (seven days) */
298 time_t exp = atoll(expires.data());
299 if ((exp < 1) || (exp > 7*24*60*60)) {
300 dout(10) << "NOTICE: exp out of range, exp = " << exp << dendl;
301 return -EPERM;
302 }
303 /* handle expiration in epoch time */
304 uint64_t req_sec = (uint64_t)internal_timegm(&date_t);
305 uint64_t now = ceph_clock_now();
306 if (now >= req_sec + exp) {
307 dout(10) << "NOTICE: now = " << now << ", req_sec = " << req_sec << ", exp = " << exp << dendl;
308 return -EPERM;
31f18b77 309 }
7c673cae 310
f67539c2 311 signedheaders = info.args.get("x-amz-signedheaders");
31f18b77
FG
312 if (signedheaders.size() == 0) {
313 return -EPERM;
314 }
7c673cae 315
f67539c2 316 signature = info.args.get("x-amz-signature");
31f18b77
FG
317 if (signature.size() == 0) {
318 return -EPERM;
319 }
7c673cae 320
f67539c2
TL
321 if (info.args.exists("x-amz-security-token")) {
322 sessiontoken = info.args.get("x-amz-security-token");
11fdf7f2
TL
323 if (sessiontoken.size() == 0) {
324 return -EPERM;
325 }
326 }
327
31f18b77 328 return 0;
7c673cae
FG
329}
330
f67539c2 331static bool get_next_token(const std::string_view& s,
31f18b77
FG
332 size_t& pos,
333 const char* const delims,
f67539c2 334 std::string_view& token)
7c673cae 335{
31f18b77 336 const size_t start = s.find_first_not_of(delims, pos);
f67539c2 337 if (start == std::string_view::npos) {
31f18b77
FG
338 pos = s.size();
339 return false;
340 }
7c673cae 341
31f18b77 342 size_t end = s.find_first_of(delims, start);
f67539c2 343 if (end != std::string_view::npos)
31f18b77
FG
344 pos = end + 1;
345 else {
346 pos = end = s.size();
347 }
348
349 token = s.substr(start, end - start);
350 return true;
351}
352
353template<std::size_t ExpectedStrNum>
f67539c2
TL
354boost::container::small_vector<std::string_view, ExpectedStrNum>
355get_str_vec(const std::string_view& str, const char* const delims)
31f18b77 356{
f67539c2 357 boost::container::small_vector<std::string_view, ExpectedStrNum> str_vec;
31f18b77
FG
358
359 size_t pos = 0;
f67539c2 360 std::string_view token;
31f18b77
FG
361 while (pos < str.size()) {
362 if (get_next_token(str, pos, delims, token)) {
363 if (token.size() > 0) {
364 str_vec.push_back(token);
365 }
366 }
367 }
368
369 return str_vec;
370}
371
372template<std::size_t ExpectedStrNum>
f67539c2
TL
373boost::container::small_vector<std::string_view, ExpectedStrNum>
374get_str_vec(const std::string_view& str)
31f18b77
FG
375{
376 const char delims[] = ";,= \t";
377 return get_str_vec<ExpectedStrNum>(str, delims);
378}
31f18b77
FG
379
380static inline int parse_v4_auth_header(const req_info& info, /* in */
f67539c2
TL
381 std::string_view& credential, /* out */
382 std::string_view& signedheaders, /* out */
383 std::string_view& signature, /* out */
384 std::string_view& date, /* out */
b3b6e05e
TL
385 std::string_view& sessiontoken, /* out */
386 const DoutPrefixProvider *dpp)
31f18b77 387{
f67539c2 388 std::string_view input(info.env->get("HTTP_AUTHORIZATION", ""));
31f18b77
FG
389 try {
390 input = input.substr(::strlen(AWS4_HMAC_SHA256_STR) + 1);
391 } catch (std::out_of_range&) {
392 /* We should never ever run into this situation as the presence of
393 * AWS4_HMAC_SHA256_STR had been verified earlier. */
b3b6e05e 394 ldpp_dout(dpp, 10) << "credentials string is too short" << dendl;
31f18b77
FG
395 return -EINVAL;
396 }
397
f67539c2 398 std::map<std::string_view, std::string_view> kv;
31f18b77
FG
399 for (const auto& s : get_str_vec<4>(input, ",")) {
400 const auto parsed_pair = parse_key_value(s);
401 if (parsed_pair) {
402 kv[parsed_pair->first] = parsed_pair->second;
403 } else {
b3b6e05e 404 ldpp_dout(dpp, 10) << "NOTICE: failed to parse auth header (s=" << s << ")"
31f18b77
FG
405 << dendl;
406 return -EINVAL;
407 }
408 }
409
f67539c2 410 static const std::array<std::string_view, 3> required_keys = {
31f18b77
FG
411 "Credential",
412 "SignedHeaders",
413 "Signature"
414 };
415
416 /* Ensure that the presigned required keys are really there. */
417 for (const auto& k : required_keys) {
418 if (kv.find(k) == std::end(kv)) {
b3b6e05e 419 ldpp_dout(dpp, 10) << "NOTICE: auth header missing key: " << k << dendl;
31f18b77
FG
420 return -EINVAL;
421 }
422 }
423
424 credential = kv["Credential"];
425 signedheaders = kv["SignedHeaders"];
426 signature = kv["Signature"];
427
428 /* sig hex str */
b3b6e05e 429 ldpp_dout(dpp, 10) << "v4 signature format = " << signature << dendl;
31f18b77
FG
430
431 /* ------------------------- handle x-amz-date header */
432
433 /* grab date */
434
435 const char *d = info.env->get("HTTP_X_AMZ_DATE");
436 struct tm t;
437 if (!parse_iso8601(d, &t, NULL, false)) {
b3b6e05e 438 ldpp_dout(dpp, 10) << "error reading date via http_x_amz_date" << dendl;
31f18b77
FG
439 return -EACCES;
440 }
441 date = d;
442
94b18763
FG
443 if (!is_time_skew_ok(internal_timegm(&t))) {
444 return -ERR_REQUEST_TIME_SKEWED;
445 }
446
11fdf7f2
TL
447 if (info.env->exists("HTTP_X_AMZ_SECURITY_TOKEN")) {
448 sessiontoken = info.env->get("HTTP_X_AMZ_SECURITY_TOKEN");
449 }
450
31f18b77
FG
451 return 0;
452}
453
20effc67
TL
454bool is_non_s3_op(RGWOpType op_type)
455{
456 if (op_type == RGW_STS_GET_SESSION_TOKEN ||
457 op_type == RGW_STS_ASSUME_ROLE ||
458 op_type == RGW_STS_ASSUME_ROLE_WEB_IDENTITY ||
459 op_type == RGW_OP_CREATE_ROLE ||
460 op_type == RGW_OP_DELETE_ROLE ||
461 op_type == RGW_OP_GET_ROLE ||
462 op_type == RGW_OP_MODIFY_ROLE ||
463 op_type == RGW_OP_LIST_ROLES ||
464 op_type == RGW_OP_PUT_ROLE_POLICY ||
465 op_type == RGW_OP_GET_ROLE_POLICY ||
466 op_type == RGW_OP_LIST_ROLE_POLICIES ||
467 op_type == RGW_OP_DELETE_ROLE_POLICY ||
468 op_type == RGW_OP_PUT_USER_POLICY ||
469 op_type == RGW_OP_GET_USER_POLICY ||
470 op_type == RGW_OP_LIST_USER_POLICIES ||
471 op_type == RGW_OP_DELETE_USER_POLICY ||
472 op_type == RGW_OP_CREATE_OIDC_PROVIDER ||
473 op_type == RGW_OP_DELETE_OIDC_PROVIDER ||
474 op_type == RGW_OP_GET_OIDC_PROVIDER ||
475 op_type == RGW_OP_LIST_OIDC_PROVIDERS ||
476 op_type == RGW_OP_PUBSUB_TOPIC_CREATE ||
477 op_type == RGW_OP_PUBSUB_TOPICS_LIST ||
478 op_type == RGW_OP_PUBSUB_TOPIC_GET ||
479 op_type == RGW_OP_PUBSUB_TOPIC_DELETE ||
480 op_type == RGW_OP_TAG_ROLE ||
481 op_type == RGW_OP_LIST_ROLE_TAGS ||
482 op_type == RGW_OP_UNTAG_ROLE) {
483 return true;
484 }
485 return false;
486}
487
a8e16298 488int parse_v4_credentials(const req_info& info, /* in */
f67539c2
TL
489 std::string_view& access_key_id, /* out */
490 std::string_view& credential_scope, /* out */
491 std::string_view& signedheaders, /* out */
492 std::string_view& signature, /* out */
493 std::string_view& date, /* out */
494 std::string_view& session_token, /* out */
b3b6e05e
TL
495 const bool using_qs, /* in */
496 const DoutPrefixProvider *dpp)
31f18b77 497{
f67539c2 498 std::string_view credential;
11fdf7f2 499 int ret;
31f18b77
FG
500 if (using_qs) {
501 ret = parse_v4_query_string(info, credential, signedheaders,
11fdf7f2 502 signature, date, session_token);
31f18b77
FG
503 } else {
504 ret = parse_v4_auth_header(info, credential, signedheaders,
b3b6e05e 505 signature, date, session_token, dpp);
31f18b77
FG
506 }
507
508 if (ret < 0) {
509 return ret;
510 }
511
11fdf7f2 512 /* access_key/YYYYMMDD/region/service/aws4_request */
b3b6e05e 513 ldpp_dout(dpp, 10) << "v4 credential format = " << credential << dendl;
31f18b77
FG
514
515 if (std::count(credential.begin(), credential.end(), '/') != 4) {
516 return -EINVAL;
517 }
518
519 /* credential must end with 'aws4_request' */
520 if (credential.find("aws4_request") == std::string::npos) {
521 return -EINVAL;
522 }
523
524 /* grab access key id */
525 const size_t pos = credential.find("/");
526 access_key_id = credential.substr(0, pos);
b3b6e05e 527 ldpp_dout(dpp, 10) << "access key id = " << access_key_id << dendl;
31f18b77
FG
528
529 /* grab credential scope */
530 credential_scope = credential.substr(pos + 1);
b3b6e05e 531 ldpp_dout(dpp, 10) << "credential scope = " << credential_scope << dendl;
31f18b77
FG
532
533 return 0;
534}
535
20effc67
TL
536string gen_v4_scope(const ceph::real_time& timestamp,
537 const string& region,
538 const string& service)
539{
540
541 auto sec = real_clock::to_time_t(timestamp);
542
543 struct tm bt;
544 gmtime_r(&sec, &bt);
545
546 auto year = 1900 + bt.tm_year;
547 auto mon = bt.tm_mon + 1;
548 auto day = bt.tm_mday;
549
550 return fmt::format(FMT_STRING("{:d}{:02d}{:02d}/{:s}/{:s}/aws4_request"),
551 year, mon, day, region, service);
552}
553
31f18b77
FG
554std::string get_v4_canonical_qs(const req_info& info, const bool using_qs)
555{
b5b8bbf5
FG
556 const std::string *params = &info.request_params;
557 std::string copy_params;
558 if (params->empty()) {
31f18b77
FG
559 /* Optimize the typical flow. */
560 return std::string();
561 }
b5b8bbf5
FG
562 if (params->find_first_of('+') != std::string::npos) {
563 copy_params = *params;
f91f0fd5 564 boost::replace_all(copy_params, "+", "%20");
b5b8bbf5
FG
565 params = &copy_params;
566 }
31f18b77
FG
567
568 /* Handle case when query string exists. Step 3 described in: http://docs.
569 * aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html */
570 std::map<std::string, std::string> canonical_qs_map;
b5b8bbf5 571 for (const auto& s : get_str_vec<5>(*params, "&")) {
f67539c2 572 std::string_view key, val;
31f18b77
FG
573 const auto parsed_pair = parse_key_value(s);
574 if (parsed_pair) {
575 std::tie(key, val) = *parsed_pair;
576 } else {
577 /* Handling a parameter without any value (even the empty one). That's
578 * it, we've encountered something like "this_param&other_param=val"
579 * which is used by S3 for subresources. */
580 key = s;
581 }
582
f67539c2 583 if (using_qs && boost::iequals(key, "X-Amz-Signature")) {
31f18b77
FG
584 /* Preserving the original behaviour of get_v4_canonical_qs() here. */
585 continue;
586 }
587
11fdf7f2
TL
588 // while awsv4 specs ask for all slashes to be encoded, s3 itself is relaxed
589 // in its implementation allowing non-url-encoded slashes to be present in
590 // presigned urls for instance
591 canonical_qs_map[aws4_uri_recode(key, true)] = aws4_uri_recode(val, true);
31f18b77
FG
592 }
593
594 /* Thanks to the early exist we have the guarantee that canonical_qs_map has
595 * at least one element. */
596 auto iter = std::begin(canonical_qs_map);
597 std::string canonical_qs;
598 canonical_qs.append(iter->first)
599 .append("=", ::strlen("="))
600 .append(iter->second);
601
602 for (iter++; iter != std::end(canonical_qs_map); iter++) {
603 canonical_qs.append("&", ::strlen("&"))
604 .append(iter->first)
605 .append("=", ::strlen("="))
606 .append(iter->second);
607 }
608
609 return canonical_qs;
610}
611
20effc67
TL
612static void add_v4_canonical_params_from_map(const map<string, string>& m,
613 std::map<string, string> *result)
614{
615 for (auto& entry : m) {
616 const auto& key = entry.first;
617 if (key.empty()) {
618 continue;
619 }
620
621 (*result)[aws4_uri_recode(key, true)] = aws4_uri_recode(entry.second, true);
622 }
623}
624
625std::string gen_v4_canonical_qs(const req_info& info)
626{
627 std::map<std::string, std::string> canonical_qs_map;
628
629 add_v4_canonical_params_from_map(info.args.get_params(), &canonical_qs_map);
630 add_v4_canonical_params_from_map(info.args.get_sys_params(), &canonical_qs_map);
631
632 if (canonical_qs_map.empty()) {
633 return string();
634 }
635
636 /* Thanks to the early exit we have the guarantee that canonical_qs_map has
637 * at least one element. */
638 auto iter = std::begin(canonical_qs_map);
639 std::string canonical_qs;
640 canonical_qs.append(iter->first)
641 .append("=", ::strlen("="))
642 .append(iter->second);
643
644 for (iter++; iter != std::end(canonical_qs_map); iter++) {
645 canonical_qs.append("&", ::strlen("&"))
646 .append(iter->first)
647 .append("=", ::strlen("="))
648 .append(iter->second);
649 }
650
651 return canonical_qs;
652}
653
31f18b77
FG
654boost::optional<std::string>
655get_v4_canonical_headers(const req_info& info,
f67539c2 656 const std::string_view& signedheaders,
31f18b77
FG
657 const bool using_qs,
658 const bool force_boto2_compat)
659{
f67539c2 660 std::map<std::string_view, std::string> canonical_hdrs_map;
31f18b77
FG
661 for (const auto& token : get_str_vec<5>(signedheaders, ";")) {
662 /* TODO(rzarzynski): we'd like to switch to sstring here but it should
663 * get push_back() and reserve() first. */
664 std::string token_env = "HTTP_";
665 token_env.reserve(token.length() + std::strlen("HTTP_") + 1);
666
667 std::transform(std::begin(token), std::end(token),
668 std::back_inserter(token_env), [](const int c) {
20effc67 669 return c == '-' ? '_' : c == '_' ? '-' : std::toupper(c);
31f18b77
FG
670 });
671
672 if (token_env == "HTTP_CONTENT_LENGTH") {
673 token_env = "CONTENT_LENGTH";
674 } else if (token_env == "HTTP_CONTENT_TYPE") {
675 token_env = "CONTENT_TYPE";
676 }
677 const char* const t = info.env->get(token_env.c_str());
678 if (!t) {
f67539c2 679 dout(10) << "warning env var not available " << token_env.c_str() << dendl;
31f18b77
FG
680 continue;
681 }
682
683 std::string token_value(t);
684 if (token_env == "HTTP_CONTENT_MD5" &&
685 !std::all_of(std::begin(token_value), std::end(token_value),
686 is_base64_for_content_md5)) {
687 dout(0) << "NOTICE: bad content-md5 provided (not base64)"
688 << ", aborting request" << dendl;
689 return boost::none;
690 }
691
692 if (force_boto2_compat && using_qs && token == "host") {
f67539c2
TL
693 std::string_view port = info.env->get("SERVER_PORT", "");
694 std::string_view secure_port = info.env->get("SERVER_PORT_SECURE", "");
31f18b77
FG
695
696 if (!secure_port.empty()) {
697 if (secure_port != "443")
698 token_value.append(":", std::strlen(":"))
699 .append(secure_port.data(), secure_port.length());
700 } else if (!port.empty()) {
701 if (port != "80")
702 token_value.append(":", std::strlen(":"))
703 .append(port.data(), port.length());
7c673cae
FG
704 }
705 }
31f18b77
FG
706
707 canonical_hdrs_map[token] = rgw_trim_whitespace(token_value);
708 }
709
710 std::string canonical_hdrs;
711 for (const auto& header : canonical_hdrs_map) {
f67539c2 712 const std::string_view& name = header.first;
28e407b8
AA
713 std::string value = header.second;
714 boost::trim_all<std::string>(value);
31f18b77
FG
715
716 canonical_hdrs.append(name.data(), name.length())
717 .append(":", std::strlen(":"))
718 .append(value)
719 .append("\n", std::strlen("\n"));
7c673cae 720 }
20effc67
TL
721 return canonical_hdrs;
722}
723
724static void handle_header(const string& header, const string& val,
725 std::map<std::string, std::string> *canonical_hdrs_map)
726{
727 /* TODO(rzarzynski): we'd like to switch to sstring here but it should
728 * get push_back() and reserve() first. */
729
730 std::string token;
731 token.reserve(header.length());
732
733 if (header == "HTTP_CONTENT_LENGTH") {
734 token = "content-length";
735 } else if (header == "HTTP_CONTENT_TYPE") {
736 token = "content-type";
737 } else {
738 auto start = std::begin(header);
739 if (boost::algorithm::starts_with(header, "HTTP_")) {
740 start += 5; /* len("HTTP_") */
741 }
742
743 std::transform(start, std::end(header),
744 std::back_inserter(token), [](const int c) {
745 return c == '_' ? '-' : std::tolower(c);
746 });
747 }
748
749 (*canonical_hdrs_map)[token] = rgw_trim_whitespace(val);
750}
751
752std::string gen_v4_canonical_headers(const req_info& info,
753 const map<string, string>& extra_headers,
754 string *signed_hdrs)
755{
756 std::map<std::string, std::string> canonical_hdrs_map;
757 for (auto& entry : info.env->get_map()) {
758 handle_header(entry.first, entry.second, &canonical_hdrs_map);
759 }
760 for (auto& entry : extra_headers) {
761 handle_header(entry.first, entry.second, &canonical_hdrs_map);
762 }
763
764 std::string canonical_hdrs;
765 signed_hdrs->clear();
766 for (const auto& header : canonical_hdrs_map) {
767 const auto& name = header.first;
768 std::string value = header.second;
769 boost::trim_all<std::string>(value);
770
771 if (!signed_hdrs->empty()) {
772 signed_hdrs->append(";");
773 }
774 signed_hdrs->append(name);
775
776 canonical_hdrs.append(name.data(), name.length())
777 .append(":", std::strlen(":"))
778 .append(value)
779 .append("\n", std::strlen("\n"));
780 }
7c673cae 781
31f18b77
FG
782 return canonical_hdrs;
783}
784
785/*
786 * create canonical request for signature version 4
787 *
788 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
789 */
790sha256_digest_t
791get_v4_canon_req_hash(CephContext* cct,
f67539c2 792 const std::string_view& http_verb,
31f18b77
FG
793 const std::string& canonical_uri,
794 const std::string& canonical_qs,
795 const std::string& canonical_hdrs,
f67539c2 796 const std::string_view& signed_hdrs,
b3b6e05e
TL
797 const std::string_view& request_payload_hash,
798 const DoutPrefixProvider *dpp)
31f18b77 799{
b3b6e05e 800 ldpp_dout(dpp, 10) << "payload request hash = " << request_payload_hash << dendl;
7c673cae 801
31f18b77
FG
802 const auto canonical_req = string_join_reserve("\n",
803 http_verb,
804 canonical_uri,
805 canonical_qs,
806 canonical_hdrs,
807 signed_hdrs,
808 request_payload_hash);
7c673cae 809
31f18b77 810 const auto canonical_req_hash = calc_hash_sha256(canonical_req);
7c673cae 811
f64942e4 812 using sanitize = rgw::crypt_sanitize::log_content;
b3b6e05e
TL
813 ldpp_dout(dpp, 10) << "canonical request = " << sanitize{canonical_req} << dendl;
814 ldpp_dout(dpp, 10) << "canonical request hash = "
11fdf7f2 815 << canonical_req_hash << dendl;
7c673cae 816
31f18b77 817 return canonical_req_hash;
7c673cae
FG
818}
819
820/*
31f18b77
FG
821 * create string to sign for signature version 4
822 *
823 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
7c673cae 824 */
31f18b77
FG
825AWSEngine::VersionAbstractor::string_to_sign_t
826get_v4_string_to_sign(CephContext* const cct,
f67539c2
TL
827 const std::string_view& algorithm,
828 const std::string_view& request_date,
829 const std::string_view& credential_scope,
b3b6e05e
TL
830 const sha256_digest_t& canonreq_hash,
831 const DoutPrefixProvider *dpp)
7c673cae 832{
11fdf7f2 833 const auto hexed_cr_hash = canonreq_hash.to_str();
f67539c2 834 const std::string_view hexed_cr_hash_str(hexed_cr_hash);
7c673cae 835
31f18b77
FG
836 const auto string_to_sign = string_join_reserve("\n",
837 algorithm,
838 request_date,
839 credential_scope,
840 hexed_cr_hash_str);
7c673cae 841
b3b6e05e 842 ldpp_dout(dpp, 10) << "string to sign = "
31f18b77
FG
843 << rgw::crypt_sanitize::log_content{string_to_sign}
844 << dendl;
7c673cae 845
31f18b77
FG
846 return string_to_sign;
847}
7c673cae 848
7c673cae 849
f67539c2
TL
850static inline std::tuple<std::string_view, /* date */
851 std::string_view, /* region */
852 std::string_view> /* service */
853parse_cred_scope(std::string_view credential_scope)
31f18b77
FG
854{
855 /* date cred */
856 size_t pos = credential_scope.find("/");
857 const auto date_cs = credential_scope.substr(0, pos);
858 credential_scope = credential_scope.substr(pos + 1);
859
860 /* region cred */
861 pos = credential_scope.find("/");
862 const auto region_cs = credential_scope.substr(0, pos);
863 credential_scope = credential_scope.substr(pos + 1);
864
865 /* service cred */
866 pos = credential_scope.find("/");
867 const auto service_cs = credential_scope.substr(0, pos);
868
869 return std::make_tuple(date_cs, region_cs, service_cs);
870}
871
872static inline std::vector<unsigned char>
f67539c2 873transform_secret_key(const std::string_view& secret_access_key)
31f18b77
FG
874{
875 /* TODO(rzarzynski): switch to constexpr when C++14 becomes available. */
876 static const std::initializer_list<unsigned char> AWS4 { 'A', 'W', 'S', '4' };
877
878 /* boost::container::small_vector might be used here if someone wants to
879 * optimize out even more dynamic allocations. */
880 std::vector<unsigned char> secret_key_utf8;
881 secret_key_utf8.reserve(AWS4.size() + secret_access_key.size());
882 secret_key_utf8.assign(AWS4);
883
884 for (const auto c : secret_access_key) {
885 std::array<unsigned char, MAX_UTF8_SZ> buf;
886 const size_t n = encode_utf8(c, buf.data());
887 secret_key_utf8.insert(std::end(secret_key_utf8),
888 std::begin(buf), std::begin(buf) + n);
889 }
890
891 return secret_key_utf8;
7c673cae
FG
892}
893
894/*
31f18b77 895 * calculate the SigningKey of AWS auth version 4
7c673cae 896 */
31f18b77
FG
897static sha256_digest_t
898get_v4_signing_key(CephContext* const cct,
f67539c2 899 const std::string_view& credential_scope,
b3b6e05e
TL
900 const std::string_view& secret_access_key,
901 const DoutPrefixProvider *dpp)
31f18b77 902{
f67539c2 903 std::string_view date, region, service;
31f18b77 904 std::tie(date, region, service) = parse_cred_scope(credential_scope);
7c673cae 905
31f18b77
FG
906 const auto utfed_sec_key = transform_secret_key(secret_access_key);
907 const auto date_k = calc_hmac_sha256(utfed_sec_key, date);
908 const auto region_k = calc_hmac_sha256(date_k, region);
909 const auto service_k = calc_hmac_sha256(region_k, service);
7c673cae 910
31f18b77
FG
911 /* aws4_request */
912 const auto signing_key = calc_hmac_sha256(service_k,
f67539c2 913 std::string_view("aws4_request"));
31f18b77 914
b3b6e05e
TL
915 ldpp_dout(dpp, 10) << "date_k = " << date_k << dendl;
916 ldpp_dout(dpp, 10) << "region_k = " << region_k << dendl;
917 ldpp_dout(dpp, 10) << "service_k = " << service_k << dendl;
918 ldpp_dout(dpp, 10) << "signing_k = " << signing_key << dendl;
31f18b77
FG
919
920 return signing_key;
7c673cae
FG
921}
922
923/*
924 * calculate the AWS signature version 4
31f18b77
FG
925 *
926 * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
927 *
928 * srv_signature_t is an alias over Ceph's basic_sstring. We're using
929 * it to keep everything within the stack boundaries instead of doing
930 * dynamic allocations.
7c673cae 931 */
31f18b77 932AWSEngine::VersionAbstractor::server_signature_t
f67539c2 933get_v4_signature(const std::string_view& credential_scope,
31f18b77 934 CephContext* const cct,
f67539c2 935 const std::string_view& secret_key,
b3b6e05e
TL
936 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign,
937 const DoutPrefixProvider *dpp)
31f18b77 938{
b3b6e05e 939 auto signing_key = get_v4_signing_key(cct, credential_scope, secret_key, dpp);
7c673cae 940
31f18b77
FG
941 /* The server-side generated digest for comparison. */
942 const auto digest = calc_hmac_sha256(signing_key, string_to_sign);
943
944 /* TODO(rzarzynski): I would love to see our sstring having reserve() and
945 * the non-const data() variant like C++17's std::string. */
946 using srv_signature_t = AWSEngine::VersionAbstractor::server_signature_t;
947 srv_signature_t signature(srv_signature_t::initialized_later(),
11fdf7f2
TL
948 digest.SIZE * 2);
949 buf_to_hex(digest.v, digest.SIZE, signature.begin());
31f18b77 950
b3b6e05e 951 ldpp_dout(dpp, 10) << "generated signature = " << signature << dendl;
31f18b77
FG
952
953 return signature;
954}
955
956AWSEngine::VersionAbstractor::server_signature_t
957get_v2_signature(CephContext* const cct,
958 const std::string& secret_key,
959 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign)
960{
961 if (secret_key.empty()) {
962 throw -EINVAL;
7c673cae
FG
963 }
964
31f18b77 965 const auto digest = calc_hmac_sha1(secret_key, string_to_sign);
7c673cae 966
31f18b77
FG
967 /* 64 is really enough */;
968 char buf[64];
969 const int ret = ceph_armor(std::begin(buf),
970 std::begin(buf) + 64,
11fdf7f2
TL
971 reinterpret_cast<const char *>(digest.v),
972 reinterpret_cast<const char *>(digest.v + digest.SIZE));
31f18b77
FG
973 if (ret < 0) {
974 ldout(cct, 10) << "ceph_armor failed" << dendl;
975 throw ret;
976 } else {
977 buf[ret] = '\0';
978 using srv_signature_t = AWSEngine::VersionAbstractor::server_signature_t;
979 return srv_signature_t(buf, ret);
980 }
981}
7c673cae 982
31f18b77
FG
983bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos) const
984{
985 return stream_pos >= (data_offset_in_stream + data_length);
986}
7c673cae 987
31f18b77
FG
988size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos) const
989{
990 if (stream_pos > (data_offset_in_stream + data_length)) {
991 /* Data in parsing_buf. */
992 return data_length;
993 } else {
994 return data_offset_in_stream + data_length - stream_pos;
995 }
996}
997
998
999/* AWSv4 completers begin. */
1000std::pair<AWSv4ComplMulti::ChunkMeta, size_t /* consumed */>
1001AWSv4ComplMulti::ChunkMeta::create_next(CephContext* const cct,
1002 ChunkMeta&& old,
1003 const char* const metabuf,
1004 const size_t metabuf_len)
1005{
f67539c2 1006 std::string_view metastr(metabuf, metabuf_len);
7c673cae 1007
31f18b77 1008 const size_t semicolon_pos = metastr.find(";");
f67539c2 1009 if (semicolon_pos == std::string_view::npos) {
31f18b77
FG
1010 ldout(cct, 20) << "AWSv4ComplMulti cannot find the ';' separator"
1011 << dendl;
1012 throw rgw::io::Exception(EINVAL, std::system_category());
7c673cae
FG
1013 }
1014
31f18b77
FG
1015 char* data_field_end;
1016 /* strtoull ignores the "\r\n" sequence after each non-first chunk. */
1017 const size_t data_length = std::strtoull(metabuf, &data_field_end, 16);
1018 if (data_length == 0 && data_field_end == metabuf) {
1019 ldout(cct, 20) << "AWSv4ComplMulti: cannot parse the data size"
1020 << dendl;
1021 throw rgw::io::Exception(EINVAL, std::system_category());
1022 }
7c673cae 1023
31f18b77
FG
1024 /* Parse the chunk_signature=... part. */
1025 const auto signature_part = metastr.substr(semicolon_pos + 1);
1026 const size_t eq_sign_pos = signature_part.find("=");
f67539c2 1027 if (eq_sign_pos == std::string_view::npos) {
31f18b77
FG
1028 ldout(cct, 20) << "AWSv4ComplMulti: cannot find the '=' separator"
1029 << dendl;
1030 throw rgw::io::Exception(EINVAL, std::system_category());
1031 }
7c673cae 1032
31f18b77
FG
1033 /* OK, we have at least the beginning of a signature. */
1034 const size_t data_sep_pos = signature_part.find("\r\n");
f67539c2 1035 if (data_sep_pos == std::string_view::npos) {
31f18b77
FG
1036 ldout(cct, 20) << "AWSv4ComplMulti: no new line at signature end"
1037 << dendl;
1038 throw rgw::io::Exception(EINVAL, std::system_category());
1039 }
7c673cae 1040
31f18b77
FG
1041 const auto signature = \
1042 signature_part.substr(eq_sign_pos + 1, data_sep_pos - 1 - eq_sign_pos);
1043 if (signature.length() != SIG_SIZE) {
1044 ldout(cct, 20) << "AWSv4ComplMulti: signature.length() != 64"
1045 << dendl;
1046 throw rgw::io::Exception(EINVAL, std::system_category());
1047 }
7c673cae 1048
31f18b77
FG
1049 const size_t data_starts_in_stream = \
1050 + semicolon_pos + strlen(";") + data_sep_pos + strlen("\r\n")
1051 + old.data_offset_in_stream + old.data_length;
7c673cae 1052
31f18b77
FG
1053 ldout(cct, 20) << "parsed new chunk; signature=" << signature
1054 << ", data_length=" << data_length
1055 << ", data_starts_in_stream=" << data_starts_in_stream
1056 << dendl;
7c673cae 1057
31f18b77
FG
1058 return std::make_pair(ChunkMeta(data_starts_in_stream,
1059 data_length,
1060 signature),
1061 semicolon_pos + 83);
1062}
7c673cae 1063
31f18b77
FG
1064std::string
1065AWSv4ComplMulti::calc_chunk_signature(const std::string& payload_hash) const
1066{
1067 const auto string_to_sign = string_join_reserve("\n",
224ce89b 1068 AWS4_HMAC_SHA256_PAYLOAD_STR,
31f18b77
FG
1069 date,
1070 credential_scope,
1071 prev_chunk_signature,
1072 AWS4_EMPTY_PAYLOAD_HASH,
1073 payload_hash);
1074
1075 ldout(cct, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
1076 << dendl;
1077
1078 /* new chunk signature */
11fdf7f2 1079 const auto sig = calc_hmac_sha256(signing_key, string_to_sign);
31f18b77 1080 /* FIXME(rzarzynski): std::string here is really unnecessary. */
11fdf7f2 1081 return sig.to_str();
31f18b77 1082}
7c673cae 1083
7c673cae 1084
31f18b77
FG
1085bool AWSv4ComplMulti::is_signature_mismatched()
1086{
1087 /* The validity of previous chunk can be verified only after getting meta-
1088 * data of the next one. */
1089 const auto payload_hash = calc_hash_sha256_restart_stream(&sha256_hash);
1090 const auto calc_signature = calc_chunk_signature(payload_hash);
1091
1092 if (chunk_meta.get_signature() != calc_signature) {
1093 ldout(cct, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
1094 << dendl;
1095 ldout(cct, 20) << "AWSv4ComplMulti: declared signature="
1096 << chunk_meta.get_signature() << dendl;
1097 ldout(cct, 20) << "AWSv4ComplMulti: calculated signature="
1098 << calc_signature << dendl;
1099
1100 return true;
1101 } else {
1102 prev_chunk_signature = chunk_meta.get_signature();
1103 return false;
1104 }
1105}
7c673cae 1106
31f18b77
FG
1107size_t AWSv4ComplMulti::recv_body(char* const buf, const size_t buf_max)
1108{
1109 /* Buffer stores only parsed stream. Raw values reflect the stream
1110 * we're getting from a client. */
1111 size_t buf_pos = 0;
1112
1113 if (chunk_meta.is_new_chunk_in_stream(stream_pos)) {
1114 /* Verify signature of the previous chunk. We aren't doing that for new
1115 * one as the procedure requires calculation of payload hash. This code
1116 * won't be triggered for the last, zero-length chunk. Instead, is will
1117 * be checked in the complete() method. */
1118 if (stream_pos >= ChunkMeta::META_MAX_SIZE && is_signature_mismatched()) {
1119 throw rgw::io::Exception(ERR_SIGNATURE_NO_MATCH, std::system_category());
1120 }
7c673cae 1121
31f18b77
FG
1122 /* We don't have metadata for this range. This means a new chunk, so we
1123 * need to parse a fresh portion of the stream. Let's start. */
1124 size_t to_extract = parsing_buf.capacity() - parsing_buf.size();
1125 do {
1126 const size_t orig_size = parsing_buf.size();
1127 parsing_buf.resize(parsing_buf.size() + to_extract);
1128 const size_t received = io_base_t::recv_body(parsing_buf.data() + orig_size,
1129 to_extract);
1130 parsing_buf.resize(parsing_buf.size() - (to_extract - received));
1131 if (received == 0) {
1132 break;
1133 }
7c673cae 1134
31f18b77
FG
1135 stream_pos += received;
1136 to_extract -= received;
1137 } while (to_extract > 0);
7c673cae 1138
31f18b77
FG
1139 size_t consumed;
1140 std::tie(chunk_meta, consumed) = \
1141 ChunkMeta::create_next(cct, std::move(chunk_meta),
1142 parsing_buf.data(), parsing_buf.size());
7c673cae 1143
31f18b77
FG
1144 /* We can drop the bytes consumed during metadata parsing. The remainder
1145 * can be chunk's data plus possibly beginning of next chunks' metadata. */
1146 parsing_buf.erase(std::begin(parsing_buf),
1147 std::begin(parsing_buf) + consumed);
1148 }
7c673cae 1149
b5b8bbf5
FG
1150 size_t stream_pos_was = stream_pos - parsing_buf.size();
1151
31f18b77 1152 size_t to_extract = \
b5b8bbf5
FG
1153 std::min(chunk_meta.get_data_size(stream_pos_was), buf_max);
1154 dout(30) << "AWSv4ComplMulti: stream_pos_was=" << stream_pos_was << ", to_extract=" << to_extract << dendl;
31f18b77
FG
1155
1156 /* It's quite probable we have a couple of real data bytes stored together
1157 * with meta-data in the parsing_buf. We need to extract them and move to
1158 * the final buffer. This is a trade-off between frontend's read overhead
1159 * and memcpy. */
1160 if (to_extract > 0 && parsing_buf.size() > 0) {
1161 const auto data_len = std::min(to_extract, parsing_buf.size());
1162 const auto data_end_iter = std::begin(parsing_buf) + data_len;
b5b8bbf5 1163 dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract << ", data_len=" << data_len << dendl;
7c673cae 1164
31f18b77
FG
1165 std::copy(std::begin(parsing_buf), data_end_iter, buf);
1166 parsing_buf.erase(std::begin(parsing_buf), data_end_iter);
7c673cae 1167
31f18b77 1168 calc_hash_sha256_update_stream(sha256_hash, buf, data_len);
7c673cae 1169
31f18b77
FG
1170 to_extract -= data_len;
1171 buf_pos += data_len;
1172 }
7c673cae 1173
31f18b77
FG
1174 /* Now we can do the bulk read directly from RestfulClient without any extra
1175 * buffering. */
1176 while (to_extract > 0) {
1177 const size_t received = io_base_t::recv_body(buf + buf_pos, to_extract);
b5b8bbf5 1178 dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract << ", received=" << received << dendl;
7c673cae 1179
31f18b77
FG
1180 if (received == 0) {
1181 break;
1182 }
7c673cae 1183
31f18b77 1184 calc_hash_sha256_update_stream(sha256_hash, buf + buf_pos, received);
7c673cae 1185
31f18b77
FG
1186 buf_pos += received;
1187 stream_pos += received;
1188 to_extract -= received;
1189 }
7c673cae 1190
31f18b77
FG
1191 dout(20) << "AWSv4ComplMulti: filled=" << buf_pos << dendl;
1192 return buf_pos;
1193}
7c673cae 1194
11fdf7f2 1195void AWSv4ComplMulti::modify_request_state(const DoutPrefixProvider* dpp, req_state* const s_rw)
31f18b77
FG
1196{
1197 const char* const decoded_length = \
1198 s_rw->info.env->get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH");
7c673cae 1199
31f18b77
FG
1200 if (!decoded_length) {
1201 throw -EINVAL;
1202 } else {
1203 s_rw->length = decoded_length;
1204 s_rw->content_length = parse_content_length(decoded_length);
1205
1206 if (s_rw->content_length < 0) {
11fdf7f2 1207 ldpp_dout(dpp, 10) << "negative AWSv4's content length, aborting" << dendl;
31f18b77
FG
1208 throw -EINVAL;
1209 }
1210 }
1211
1212 /* Install the filter over rgw::io::RestfulClient. */
1213 AWS_AUTHv4_IO(s_rw)->add_filter(
1214 std::static_pointer_cast<io_base_t>(shared_from_this()));
1215}
1216
1217bool AWSv4ComplMulti::complete()
1218{
1219 /* Now it's time to verify the signature of the last, zero-length chunk. */
1220 if (is_signature_mismatched()) {
1221 ldout(cct, 10) << "ERROR: signature of last chunk does not match"
1222 << dendl;
1223 return false;
1224 } else {
1225 return true;
1226 }
1227}
1228
1229rgw::auth::Completer::cmplptr_t
1230AWSv4ComplMulti::create(const req_state* const s,
f67539c2
TL
1231 std::string_view date,
1232 std::string_view credential_scope,
1233 std::string_view seed_signature,
31f18b77
FG
1234 const boost::optional<std::string>& secret_key)
1235{
1236 if (!secret_key) {
1237 /* Some external authorizers (like Keystone) aren't fully compliant with
1238 * AWSv4. They do not provide the secret_key which is necessary to handle
1239 * the streamed upload. */
1240 throw -ERR_NOT_IMPLEMENTED;
1241 }
1242
1243 const auto signing_key = \
b3b6e05e 1244 rgw::auth::s3::get_v4_signing_key(s->cct, credential_scope, *secret_key, s);
31f18b77
FG
1245
1246 return std::make_shared<AWSv4ComplMulti>(s,
1247 std::move(date),
1248 std::move(credential_scope),
1249 std::move(seed_signature),
1250 signing_key);
7c673cae 1251}
31f18b77
FG
1252
1253size_t AWSv4ComplSingle::recv_body(char* const buf, const size_t max)
1254{
1255 const auto received = io_base_t::recv_body(buf, max);
1256 calc_hash_sha256_update_stream(sha256_hash, buf, received);
1257
1258 return received;
1259}
1260
11fdf7f2 1261void AWSv4ComplSingle::modify_request_state(const DoutPrefixProvider* dpp, req_state* const s_rw)
31f18b77
FG
1262{
1263 /* Install the filter over rgw::io::RestfulClient. */
1264 AWS_AUTHv4_IO(s_rw)->add_filter(
1265 std::static_pointer_cast<io_base_t>(shared_from_this()));
1266}
1267
1268bool AWSv4ComplSingle::complete()
1269{
1270 /* The completer is only for the cases where signed payload has been
1271 * requested. It won't be used, for instance, during the query string-based
1272 * authentication. */
1273 const auto payload_hash = calc_hash_sha256_close_stream(&sha256_hash);
1274
1275 /* Validate x-amz-sha256 */
1276 if (payload_hash.compare(expected_request_payload_hash) == 0) {
1277 return true;
1278 } else {
1279 ldout(cct, 10) << "ERROR: x-amz-content-sha256 does not match"
1280 << dendl;
1281 ldout(cct, 10) << "ERROR: grab_aws4_sha256_hash()="
1282 << payload_hash << dendl;
1283 ldout(cct, 10) << "ERROR: expected_request_payload_hash="
1284 << expected_request_payload_hash << dendl;
1285 return false;
1286 }
1287}
1288
1289AWSv4ComplSingle::AWSv4ComplSingle(const req_state* const s)
1290 : io_base_t(nullptr),
1291 cct(s->cct),
1292 expected_request_payload_hash(get_v4_exp_payload_hash(s->info)),
1293 sha256_hash(calc_hash_sha256_open_stream()) {
1294}
1295
1296rgw::auth::Completer::cmplptr_t
1297AWSv4ComplSingle::create(const req_state* const s,
1298 const boost::optional<std::string>&)
1299{
1300 return std::make_shared<AWSv4ComplSingle>(s);
1301}
1302
9f95a23c 1303} // namespace rgw::auth::s3