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