1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
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"
16 #include "rgw_crypt_sanitize.h"
18 #include <boost/container/small_vector.hpp>
19 #include <boost/utility/string_view.hpp>
21 #define dout_context g_ceph_context
22 #define dout_subsys ceph_subsys_rgw
24 static const auto signed_subresources
= {
35 "response-cache-control",
36 "response-content-disposition",
37 "response-content-encoding",
38 "response-content-language",
39 "response-content-type",
52 * ?get the canonical amazon-style header for something?
56 get_canon_amz_hdr(const std::map
<std::string
, std::string
>& meta_map
)
60 for (const auto& kv
: meta_map
) {
61 dest
.append(kv
.first
);
63 dest
.append(kv
.second
);
71 * ?get the canonical representation of the object's location
74 get_canon_resource(const char* const request_uri
,
75 const std::map
<std::string
, std::string
>& sub_resources
)
80 dest
.append(request_uri
);
84 for (const auto& subresource
: signed_subresources
) {
85 const auto iter
= sub_resources
.find(subresource
);
86 if (iter
== std::end(sub_resources
)) {
97 dest
.append(iter
->first
);
98 if (! iter
->second
.empty()) {
100 dest
.append(iter
->second
);
104 dout(10) << "get_canon_resource(): dest=" << dest
<< dendl
;
109 * get the header authentication information required to
110 * compute a request's signature
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
)
130 dest
.append(content_md5
);
135 dest
.append(content_type
);
144 dest
.append(get_canon_amz_hdr(meta_map
));
145 dest
.append(get_canon_resource(request_uri
, sub_resources
));
150 static inline bool is_base64_for_content_md5(unsigned char c
) {
151 return (isalnum(c
) || isspace(c
) || (c
== '+') || (c
== '/') || (c
== '='));
155 * get the header authentication information required to
156 * compute a request's signature
158 bool rgw_create_s3_canonical_header(const req_info
& info
,
159 utime_t
* const header_time
,
163 const char* const content_md5
= info
.env
->get("HTTP_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
;
174 const char *content_type
= info
.env
->get("CONTENT_TYPE");
178 date
= info
.args
.get("Expires");
180 const char *str
= info
.env
->get("HTTP_X_AMZ_DATE");
181 const char *req_date
= str
;
183 req_date
= info
.env
->get("HTTP_DATE");
185 dout(0) << "NOTICE: missing date for auth header" << dendl
;
193 if (!parse_rfc2616(req_date
, &t
)) {
194 dout(0) << "NOTICE: failed to parse date for auth header" << dendl
;
197 if (t
.tm_year
< 70) {
198 dout(0) << "NOTICE: bad date (predates epoch): " << req_date
<< dendl
;
201 *header_time
= utime_t(internal_timegm(&t
), 0);
205 const auto& meta_map
= info
.x_meta_map
;
206 const auto& sub_resources
= info
.args
.get_sub_resources();
208 std::string request_uri
;
209 if (info
.effective_uri
.empty()) {
210 request_uri
= info
.request_uri
;
212 request_uri
= info
.effective_uri
;
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
);
226 /* FIXME(rzarzynski): duplicated from rgw_rest_s3.h. */
227 #define RGW_AUTH_GRACE_MINS 15
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 */
235 /* auth ships with req params ... */
237 /* look for required params */
238 credential
= info
.args
.get("X-Amz-Credential");
239 if (credential
.size() == 0) {
243 date
= info
.args
.get("X-Amz-Date");
245 if (!parse_iso8601(sview2cstr(date
).data(), &date_t
, nullptr, false)) {
249 /* Used for pre-signatured url, We shouldn't return -ERR_REQUEST_TIME_SKEWED
250 * when current time <= X-Amz-Expires */
253 uint64_t now_req
= 0;
254 uint64_t now
= ceph_clock_now();
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
;
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
;
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
;
286 signedheaders
= info
.args
.get("X-Amz-SignedHeaders");
287 if (signedheaders
.size() == 0) {
291 signature
= info
.args
.get("X-Amz-Signature");
292 if (signature
.size() == 0) {
300 static bool get_next_token(const boost::string_view
& s
,
302 const char* const delims
,
303 boost::string_view
& token
)
305 const size_t start
= s
.find_first_not_of(delims
, pos
);
306 if (start
== boost::string_view::npos
) {
311 size_t end
= s
.find_first_of(delims
, start
);
312 if (end
!= boost::string_view::npos
)
315 pos
= end
= s
.size();
318 token
= s
.substr(start
, end
- start
);
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
)
326 boost::container::small_vector
<boost::string_view
, ExpectedStrNum
> str_vec
;
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
);
341 template<std::size_t ExpectedStrNum
>
342 boost::container::small_vector
<boost::string_view
, ExpectedStrNum
>
343 get_str_vec(const boost::string_view
& str
)
345 const char delims
[] = ";,= \t";
346 return get_str_vec
<ExpectedStrNum
>(str
, delims
);
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 */
356 boost::string_view
input(info
.env
->get("HTTP_AUTHORIZATION", ""));
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
;
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
);
370 kv
[parsed_pair
->first
] = parsed_pair
->second
;
372 dout(10) << "NOTICE: failed to parse auth header (s=" << s
<< ")"
378 static const std::array
<boost::string_view
, 3> required_keys
= {
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
;
392 credential
= kv
["Credential"];
393 signedheaders
= kv
["SignedHeaders"];
394 signature
= kv
["Signature"];
397 dout(10) << "v4 signature format = " << signature
<< dendl
;
399 /* ------------------------- handle x-amz-date header */
403 const char *d
= info
.env
->get("HTTP_X_AMZ_DATE");
405 if (!parse_iso8601(d
, &t
, NULL
, false)) {
406 dout(10) << "error reading date via http_x_amz_date" << dendl
;
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 */
422 const char* const http_auth
= info
.env
->get("HTTP_AUTHORIZATION");
423 using_qs
= http_auth
== nullptr || http_auth
[0] == '\0';
426 boost::string_view credential
;
428 ret
= parse_v4_query_string(info
, credential
, signedheaders
,
431 ret
= parse_v4_auth_header(info
, credential
, signedheaders
,
439 /* AKIAIVKTAZLOCF43WNQD/AAAAMMDD/region/host/aws4_request */
440 dout(10) << "v4 credential format = " << credential
<< dendl
;
442 if (std::count(credential
.begin(), credential
.end(), '/') != 4) {
446 /* credential must end with 'aws4_request' */
447 if (credential
.find("aws4_request") == std::string::npos
) {
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
;
456 /* grab credential scope */
457 credential_scope
= credential
.substr(pos
+ 1);
458 dout(10) << "credential scope = " << credential_scope
<< dendl
;
463 static inline bool char_needs_aws4_escaping(const char c
)
465 if ((c
>= 'a' && c
<= 'z') ||
466 (c
>= 'A' && c
<= 'Z') ||
467 (c
>= '0' && c
<= '9')) {
481 static inline std::string
aws4_uri_encode(const std::string
& src
)
485 for (const std::string::value_type c
: src
) {
486 if (char_needs_aws4_escaping(c
)) {
487 rgw_uri_escape_char(c
, result
);
496 static inline std::string
aws4_uri_recode(const boost::string_view
& src
)
498 std::string decoded
= url_decode(src
);
499 return aws4_uri_encode(decoded
);
502 std::string
get_v4_canonical_qs(const req_info
& info
, const bool using_qs
)
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();
510 if (params
->find_first_of('+') != std::string::npos
) {
511 copy_params
= *params
;
512 boost::replace_all(copy_params
, "+", " ");
513 params
= ©_params
;
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
);
523 std::tie(key
, val
) = *parsed_pair
;
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. */
531 if (using_qs
&& key
== "X-Amz-Signature") {
532 /* Preserving the original behaviour of get_v4_canonical_qs() here. */
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
540 canonical_qs_map
[key
.to_string()] = val
.to_string();
542 canonical_qs_map
[aws4_uri_recode(key
)] = aws4_uri_recode(val
);
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
);
554 for (iter
++; iter
!= std::end(canonical_qs_map
); iter
++) {
555 canonical_qs
.append("&", ::strlen("&"))
557 .append("=", ::strlen("="))
558 .append(iter
->second
);
564 boost::optional
<std::string
>
565 get_v4_canonical_headers(const req_info
& info
,
566 const boost::string_view
& signedheaders
,
568 const bool force_boto2_compat
)
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);
577 std::transform(std::begin(token
), std::end(token
),
578 std::back_inserter(token_env
), [](const int c
) {
579 return c
== '-' ? '_' : std::toupper(c
);
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";
587 const char* const t
= info
.env
->get(token_env
.c_str());
589 dout(10) << "warning env var not available" << dendl
;
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
;
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", "");
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()) {
612 token_value
.append(":", std::strlen(":"))
613 .append(port
.data(), port
.length());
617 canonical_hdrs_map
[token
] = rgw_trim_whitespace(token_value
);
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
;
625 canonical_hdrs
.append(name
.data(), name
.length())
626 .append(":", std::strlen(":"))
628 .append("\n", std::strlen("\n"));
631 return canonical_hdrs
;
635 * create canonical request for signature version 4
637 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
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
)
648 ldout(cct
, 10) << "payload request hash = " << request_payload_hash
<< dendl
;
650 const auto canonical_req
= string_join_reserve("\n",
656 request_payload_hash
);
658 const auto canonical_req_hash
= calc_hash_sha256(canonical_req
);
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
;
664 return canonical_req_hash
;
668 * create string to sign for signature version 4
670 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
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
)
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);
683 const auto string_to_sign
= string_join_reserve("\n",
689 ldout(cct
, 10) << "string to sign = "
690 << rgw::crypt_sanitize::log_content
{string_to_sign
}
693 return string_to_sign
;
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
)
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);
708 pos
= credential_scope
.find("/");
709 const auto region_cs
= credential_scope
.substr(0, pos
);
710 credential_scope
= credential_scope
.substr(pos
+ 1);
713 pos
= credential_scope
.find("/");
714 const auto service_cs
= credential_scope
.substr(0, pos
);
716 return std::make_tuple(date_cs
, region_cs
, service_cs
);
719 static inline std::vector
<unsigned char>
720 transform_secret_key(const boost::string_view
& secret_access_key
)
722 /* TODO(rzarzynski): switch to constexpr when C++14 becomes available. */
723 static const std::initializer_list
<unsigned char> AWS4
{ 'A', 'W', 'S', '4' };
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
);
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
);
738 return secret_key_utf8
;
742 * calculate the SigningKey of AWS auth version 4
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
)
749 boost::string_view date
, region
, service
;
750 std::tie(date
, region
, service
) = parse_cred_scope(credential_scope
);
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
);
758 const auto signing_key
= calc_hmac_sha256(service_k
,
759 boost::string_view("aws4_request"));
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
;
770 * calculate the AWS signature version 4
772 * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
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.
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
)
784 auto signing_key
= get_v4_signing_key(cct
, credential_scope
, secret_key
);
786 /* The server-side generated digest for comparison. */
787 const auto digest
= calc_hmac_sha256(signing_key
, string_to_sign
);
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(),
794 buf_to_hex(digest
.data(), digest
.size(), signature
.begin());
796 ldout(cct
, 10) << "generated signature = " << signature
<< dendl
;
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
)
806 if (secret_key
.empty()) {
810 const auto digest
= calc_hmac_sha1(secret_key
, string_to_sign
);
812 /* 64 is really enough */;
814 const int ret
= ceph_armor(std::begin(buf
),
815 std::begin(buf
) + 64,
817 std::begin(digest
) + digest
.size());
819 ldout(cct
, 10) << "ceph_armor failed" << dendl
;
823 using srv_signature_t
= AWSEngine::VersionAbstractor::server_signature_t
;
824 return srv_signature_t(buf
, ret
);
828 bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos
) const
830 return stream_pos
>= (data_offset_in_stream
+ data_length
);
833 size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos
) const
835 if (stream_pos
> (data_offset_in_stream
+ data_length
)) {
836 /* Data in parsing_buf. */
839 return data_offset_in_stream
+ data_length
- stream_pos
;
844 /* AWSv4 completers begin. */
845 std::pair
<AWSv4ComplMulti::ChunkMeta
, size_t /* consumed */>
846 AWSv4ComplMulti::ChunkMeta::create_next(CephContext
* const cct
,
848 const char* const metabuf
,
849 const size_t metabuf_len
)
851 boost::string_ref
metastr(metabuf
, metabuf_len
);
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"
857 throw rgw::io::Exception(EINVAL
, std::system_category());
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"
866 throw rgw::io::Exception(EINVAL
, std::system_category());
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"
875 throw rgw::io::Exception(EINVAL
, std::system_category());
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"
883 throw rgw::io::Exception(EINVAL
, std::system_category());
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"
891 throw rgw::io::Exception(EINVAL
, std::system_category());
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
;
898 ldout(cct
, 20) << "parsed new chunk; signature=" << signature
899 << ", data_length=" << data_length
900 << ", data_starts_in_stream=" << data_starts_in_stream
903 return std::make_pair(ChunkMeta(data_starts_in_stream
,
910 AWSv4ComplMulti::calc_chunk_signature(const std::string
& payload_hash
) const
912 const auto string_to_sign
= string_join_reserve("\n",
913 AWS4_HMAC_SHA256_PAYLOAD_STR
,
916 prev_chunk_signature
,
917 AWS4_EMPTY_PAYLOAD_HASH
,
920 ldout(cct
, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
923 /* new chunk signature */
924 const auto sighex
= buf_to_hex(calc_hmac_sha256(signing_key
,
926 /* FIXME(rzarzynski): std::string here is really unnecessary. */
927 return std::string(sighex
.data(), sighex
.size() - 1);
931 bool AWSv4ComplMulti::is_signature_mismatched()
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
);
938 if (chunk_meta
.get_signature() != calc_signature
) {
939 ldout(cct
, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
941 ldout(cct
, 20) << "AWSv4ComplMulti: declared signature="
942 << chunk_meta
.get_signature() << dendl
;
943 ldout(cct
, 20) << "AWSv4ComplMulti: calculated signature="
944 << calc_signature
<< dendl
;
948 prev_chunk_signature
= chunk_meta
.get_signature();
953 size_t AWSv4ComplMulti::recv_body(char* const buf
, const size_t buf_max
)
955 /* Buffer stores only parsed stream. Raw values reflect the stream
956 * we're getting from a client. */
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());
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();
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
,
976 parsing_buf
.resize(parsing_buf
.size() - (to_extract
- received
));
981 stream_pos
+= received
;
982 to_extract
-= received
;
983 } while (to_extract
> 0);
986 std::tie(chunk_meta
, consumed
) = \
987 ChunkMeta::create_next(cct
, std::move(chunk_meta
),
988 parsing_buf
.data(), parsing_buf
.size());
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
);
996 size_t stream_pos_was
= stream_pos
- parsing_buf
.size();
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
;
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
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
;
1011 std::copy(std::begin(parsing_buf
), data_end_iter
, buf
);
1012 parsing_buf
.erase(std::begin(parsing_buf
), data_end_iter
);
1014 calc_hash_sha256_update_stream(sha256_hash
, buf
, data_len
);
1016 to_extract
-= data_len
;
1017 buf_pos
+= data_len
;
1020 /* Now we can do the bulk read directly from RestfulClient without any extra
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
;
1026 if (received
== 0) {
1030 calc_hash_sha256_update_stream(sha256_hash
, buf
+ buf_pos
, received
);
1032 buf_pos
+= received
;
1033 stream_pos
+= received
;
1034 to_extract
-= received
;
1037 dout(20) << "AWSv4ComplMulti: filled=" << buf_pos
<< dendl
;
1041 void AWSv4ComplMulti::modify_request_state(req_state
* const s_rw
)
1043 const char* const decoded_length
= \
1044 s_rw
->info
.env
->get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH");
1046 if (!decoded_length
) {
1049 s_rw
->length
= decoded_length
;
1050 s_rw
->content_length
= parse_content_length(decoded_length
);
1052 if (s_rw
->content_length
< 0) {
1053 ldout(cct
, 10) << "negative AWSv4's content length, aborting" << dendl
;
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()));
1063 bool AWSv4ComplMulti::complete()
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"
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
)
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
;
1089 const auto signing_key
= \
1090 rgw::auth::s3::get_v4_signing_key(s
->cct
, credential_scope
, *secret_key
);
1092 return std::make_shared
<AWSv4ComplMulti
>(s
,
1094 std::move(credential_scope
),
1095 std::move(seed_signature
),
1099 size_t AWSv4ComplSingle::recv_body(char* const buf
, const size_t max
)
1101 const auto received
= io_base_t::recv_body(buf
, max
);
1102 calc_hash_sha256_update_stream(sha256_hash
, buf
, received
);
1107 void AWSv4ComplSingle::modify_request_state(req_state
* const s_rw
)
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()));
1114 bool AWSv4ComplSingle::complete()
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
);
1121 /* Validate x-amz-sha256 */
1122 if (payload_hash
.compare(expected_request_payload_hash
) == 0) {
1125 ldout(cct
, 10) << "ERROR: x-amz-content-sha256 does not match"
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
;
1135 AWSv4ComplSingle::AWSv4ComplSingle(const req_state
* const s
)
1136 : io_base_t(nullptr),
1138 expected_request_payload_hash(get_v4_exp_payload_hash(s
->info
)),
1139 sha256_hash(calc_hash_sha256_open_stream()) {
1142 rgw::auth::Completer::cmplptr_t
1143 AWSv4ComplSingle::create(const req_state
* const s
,
1144 const boost::optional
<std::string
>&)
1146 return std::make_shared
<AWSv4ComplSingle
>(s
);
1149 } /* namespace s3 */
1150 } /* namespace auth */
1151 } /* namespace rgw */