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