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 if (info
.request_params
.empty()) {
505 /* Optimize the typical flow. */
506 return std::string();
509 /* Handle case when query string exists. Step 3 described in: http://docs.
510 * aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html */
511 std::map
<std::string
, std::string
> canonical_qs_map
;
512 for (const auto& s
: get_str_vec
<5>(info
.request_params
, "&")) {
513 boost::string_view key
, val
;
514 const auto parsed_pair
= parse_key_value(s
);
516 std::tie(key
, val
) = *parsed_pair
;
518 /* Handling a parameter without any value (even the empty one). That's
519 * it, we've encountered something like "this_param&other_param=val"
520 * which is used by S3 for subresources. */
524 if (using_qs
&& key
== "X-Amz-Signature") {
525 /* Preserving the original behaviour of get_v4_canonical_qs() here. */
529 if (key
== "X-Amz-Credential") {
530 /* FIXME(rzarzynski): I can't find any comment in the previously linked
531 * Amazon's docs saying that X-Amz-Credential should be handled in this
533 canonical_qs_map
[key
.to_string()] = val
.to_string();
535 canonical_qs_map
[aws4_uri_recode(key
)] = aws4_uri_recode(val
);
539 /* Thanks to the early exist we have the guarantee that canonical_qs_map has
540 * at least one element. */
541 auto iter
= std::begin(canonical_qs_map
);
542 std::string canonical_qs
;
543 canonical_qs
.append(iter
->first
)
544 .append("=", ::strlen("="))
545 .append(iter
->second
);
547 for (iter
++; iter
!= std::end(canonical_qs_map
); iter
++) {
548 canonical_qs
.append("&", ::strlen("&"))
550 .append("=", ::strlen("="))
551 .append(iter
->second
);
557 boost::optional
<std::string
>
558 get_v4_canonical_headers(const req_info
& info
,
559 const boost::string_view
& signedheaders
,
561 const bool force_boto2_compat
)
563 std::map
<boost::string_view
, std::string
> canonical_hdrs_map
;
564 for (const auto& token
: get_str_vec
<5>(signedheaders
, ";")) {
565 /* TODO(rzarzynski): we'd like to switch to sstring here but it should
566 * get push_back() and reserve() first. */
567 std::string token_env
= "HTTP_";
568 token_env
.reserve(token
.length() + std::strlen("HTTP_") + 1);
570 std::transform(std::begin(token
), std::end(token
),
571 std::back_inserter(token_env
), [](const int c
) {
572 return c
== '-' ? '_' : std::toupper(c
);
575 if (token_env
== "HTTP_CONTENT_LENGTH") {
576 token_env
= "CONTENT_LENGTH";
577 } else if (token_env
== "HTTP_CONTENT_TYPE") {
578 token_env
= "CONTENT_TYPE";
580 const char* const t
= info
.env
->get(token_env
.c_str());
582 dout(10) << "warning env var not available" << dendl
;
586 std::string
token_value(t
);
587 if (token_env
== "HTTP_CONTENT_MD5" &&
588 !std::all_of(std::begin(token_value
), std::end(token_value
),
589 is_base64_for_content_md5
)) {
590 dout(0) << "NOTICE: bad content-md5 provided (not base64)"
591 << ", aborting request" << dendl
;
595 if (force_boto2_compat
&& using_qs
&& token
== "host") {
596 boost::string_view port
= info
.env
->get("SERVER_PORT", "");
597 boost::string_view secure_port
= info
.env
->get("SERVER_PORT_SECURE", "");
599 if (!secure_port
.empty()) {
600 if (secure_port
!= "443")
601 token_value
.append(":", std::strlen(":"))
602 .append(secure_port
.data(), secure_port
.length());
603 } else if (!port
.empty()) {
605 token_value
.append(":", std::strlen(":"))
606 .append(port
.data(), port
.length());
610 canonical_hdrs_map
[token
] = rgw_trim_whitespace(token_value
);
613 std::string canonical_hdrs
;
614 for (const auto& header
: canonical_hdrs_map
) {
615 const boost::string_view
& name
= header
.first
;
616 const std::string
& value
= header
.second
;
618 canonical_hdrs
.append(name
.data(), name
.length())
619 .append(":", std::strlen(":"))
621 .append("\n", std::strlen("\n"));
624 return canonical_hdrs
;
628 * create canonical request for signature version 4
630 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
633 get_v4_canon_req_hash(CephContext
* cct
,
634 const boost::string_view
& http_verb
,
635 const std::string
& canonical_uri
,
636 const std::string
& canonical_qs
,
637 const std::string
& canonical_hdrs
,
638 const boost::string_view
& signed_hdrs
,
639 const boost::string_view
& request_payload_hash
)
641 ldout(cct
, 10) << "payload request hash = " << request_payload_hash
<< dendl
;
643 const auto canonical_req
= string_join_reserve("\n",
649 request_payload_hash
);
651 const auto canonical_req_hash
= calc_hash_sha256(canonical_req
);
653 ldout(cct
, 10) << "canonical request = " << canonical_req
<< dendl
;
654 ldout(cct
, 10) << "canonical request hash = "
655 << buf_to_hex(canonical_req_hash
).data() << dendl
;
657 return canonical_req_hash
;
661 * create string to sign for signature version 4
663 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
665 AWSEngine::VersionAbstractor::string_to_sign_t
666 get_v4_string_to_sign(CephContext
* const cct
,
667 const boost::string_view
& algorithm
,
668 const boost::string_view
& request_date
,
669 const boost::string_view
& credential_scope
,
670 const sha256_digest_t
& canonreq_hash
)
672 const auto hexed_cr_hash
= buf_to_hex(canonreq_hash
);
673 const boost::string_view
hexed_cr_hash_str(hexed_cr_hash
.data(),
674 hexed_cr_hash
.size() - 1);
676 const auto string_to_sign
= string_join_reserve("\n",
682 ldout(cct
, 10) << "string to sign = "
683 << rgw::crypt_sanitize::log_content
{string_to_sign
}
686 return string_to_sign
;
690 static inline std::tuple
<boost::string_view
, /* date */
691 boost::string_view
, /* region */
692 boost::string_view
> /* service */
693 parse_cred_scope(boost::string_view credential_scope
)
696 size_t pos
= credential_scope
.find("/");
697 const auto date_cs
= credential_scope
.substr(0, pos
);
698 credential_scope
= credential_scope
.substr(pos
+ 1);
701 pos
= credential_scope
.find("/");
702 const auto region_cs
= credential_scope
.substr(0, pos
);
703 credential_scope
= credential_scope
.substr(pos
+ 1);
706 pos
= credential_scope
.find("/");
707 const auto service_cs
= credential_scope
.substr(0, pos
);
709 return std::make_tuple(date_cs
, region_cs
, service_cs
);
712 static inline std::vector
<unsigned char>
713 transform_secret_key(const boost::string_view
& secret_access_key
)
715 /* TODO(rzarzynski): switch to constexpr when C++14 becomes available. */
716 static const std::initializer_list
<unsigned char> AWS4
{ 'A', 'W', 'S', '4' };
718 /* boost::container::small_vector might be used here if someone wants to
719 * optimize out even more dynamic allocations. */
720 std::vector
<unsigned char> secret_key_utf8
;
721 secret_key_utf8
.reserve(AWS4
.size() + secret_access_key
.size());
722 secret_key_utf8
.assign(AWS4
);
724 for (const auto c
: secret_access_key
) {
725 std::array
<unsigned char, MAX_UTF8_SZ
> buf
;
726 const size_t n
= encode_utf8(c
, buf
.data());
727 secret_key_utf8
.insert(std::end(secret_key_utf8
),
728 std::begin(buf
), std::begin(buf
) + n
);
731 return secret_key_utf8
;
735 * calculate the SigningKey of AWS auth version 4
737 static sha256_digest_t
738 get_v4_signing_key(CephContext
* const cct
,
739 const boost::string_view
& credential_scope
,
740 const boost::string_view
& secret_access_key
)
742 boost::string_view date
, region
, service
;
743 std::tie(date
, region
, service
) = parse_cred_scope(credential_scope
);
745 const auto utfed_sec_key
= transform_secret_key(secret_access_key
);
746 const auto date_k
= calc_hmac_sha256(utfed_sec_key
, date
);
747 const auto region_k
= calc_hmac_sha256(date_k
, region
);
748 const auto service_k
= calc_hmac_sha256(region_k
, service
);
751 const auto signing_key
= calc_hmac_sha256(service_k
,
752 boost::string_view("aws4_request"));
754 ldout(cct
, 10) << "date_k = " << buf_to_hex(date_k
).data() << dendl
;
755 ldout(cct
, 10) << "region_k = " << buf_to_hex(region_k
).data() << dendl
;
756 ldout(cct
, 10) << "service_k = " << buf_to_hex(service_k
).data() << dendl
;
757 ldout(cct
, 10) << "signing_k = " << buf_to_hex(signing_key
).data() << dendl
;
763 * calculate the AWS signature version 4
765 * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
767 * srv_signature_t is an alias over Ceph's basic_sstring. We're using
768 * it to keep everything within the stack boundaries instead of doing
769 * dynamic allocations.
771 AWSEngine::VersionAbstractor::server_signature_t
772 get_v4_signature(const boost::string_view
& credential_scope
,
773 CephContext
* const cct
,
774 const boost::string_view
& secret_key
,
775 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
)
777 auto signing_key
= get_v4_signing_key(cct
, credential_scope
, secret_key
);
779 /* The server-side generated digest for comparison. */
780 const auto digest
= calc_hmac_sha256(signing_key
, string_to_sign
);
782 /* TODO(rzarzynski): I would love to see our sstring having reserve() and
783 * the non-const data() variant like C++17's std::string. */
784 using srv_signature_t
= AWSEngine::VersionAbstractor::server_signature_t
;
785 srv_signature_t
signature(srv_signature_t::initialized_later(),
787 buf_to_hex(digest
.data(), digest
.size(), signature
.begin());
789 ldout(cct
, 10) << "generated signature = " << signature
<< dendl
;
794 AWSEngine::VersionAbstractor::server_signature_t
795 get_v2_signature(CephContext
* const cct
,
796 const std::string
& secret_key
,
797 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
)
799 if (secret_key
.empty()) {
803 const auto digest
= calc_hmac_sha1(secret_key
, string_to_sign
);
805 /* 64 is really enough */;
807 const int ret
= ceph_armor(std::begin(buf
),
808 std::begin(buf
) + 64,
810 std::begin(digest
) + digest
.size());
812 ldout(cct
, 10) << "ceph_armor failed" << dendl
;
816 using srv_signature_t
= AWSEngine::VersionAbstractor::server_signature_t
;
817 return srv_signature_t(buf
, ret
);
821 bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos
) const
823 return stream_pos
>= (data_offset_in_stream
+ data_length
);
826 size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos
) const
828 if (stream_pos
> (data_offset_in_stream
+ data_length
)) {
829 /* Data in parsing_buf. */
832 return data_offset_in_stream
+ data_length
- stream_pos
;
837 /* AWSv4 completers begin. */
838 std::pair
<AWSv4ComplMulti::ChunkMeta
, size_t /* consumed */>
839 AWSv4ComplMulti::ChunkMeta::create_next(CephContext
* const cct
,
841 const char* const metabuf
,
842 const size_t metabuf_len
)
844 boost::string_ref
metastr(metabuf
, metabuf_len
);
846 const size_t semicolon_pos
= metastr
.find(";");
847 if (semicolon_pos
== boost::string_ref::npos
) {
848 ldout(cct
, 20) << "AWSv4ComplMulti cannot find the ';' separator"
850 throw rgw::io::Exception(EINVAL
, std::system_category());
853 char* data_field_end
;
854 /* strtoull ignores the "\r\n" sequence after each non-first chunk. */
855 const size_t data_length
= std::strtoull(metabuf
, &data_field_end
, 16);
856 if (data_length
== 0 && data_field_end
== metabuf
) {
857 ldout(cct
, 20) << "AWSv4ComplMulti: cannot parse the data size"
859 throw rgw::io::Exception(EINVAL
, std::system_category());
862 /* Parse the chunk_signature=... part. */
863 const auto signature_part
= metastr
.substr(semicolon_pos
+ 1);
864 const size_t eq_sign_pos
= signature_part
.find("=");
865 if (eq_sign_pos
== boost::string_ref::npos
) {
866 ldout(cct
, 20) << "AWSv4ComplMulti: cannot find the '=' separator"
868 throw rgw::io::Exception(EINVAL
, std::system_category());
871 /* OK, we have at least the beginning of a signature. */
872 const size_t data_sep_pos
= signature_part
.find("\r\n");
873 if (data_sep_pos
== boost::string_ref::npos
) {
874 ldout(cct
, 20) << "AWSv4ComplMulti: no new line at signature end"
876 throw rgw::io::Exception(EINVAL
, std::system_category());
879 const auto signature
= \
880 signature_part
.substr(eq_sign_pos
+ 1, data_sep_pos
- 1 - eq_sign_pos
);
881 if (signature
.length() != SIG_SIZE
) {
882 ldout(cct
, 20) << "AWSv4ComplMulti: signature.length() != 64"
884 throw rgw::io::Exception(EINVAL
, std::system_category());
887 const size_t data_starts_in_stream
= \
888 + semicolon_pos
+ strlen(";") + data_sep_pos
+ strlen("\r\n")
889 + old
.data_offset_in_stream
+ old
.data_length
;
891 ldout(cct
, 20) << "parsed new chunk; signature=" << signature
892 << ", data_length=" << data_length
893 << ", data_starts_in_stream=" << data_starts_in_stream
896 return std::make_pair(ChunkMeta(data_starts_in_stream
,
903 AWSv4ComplMulti::calc_chunk_signature(const std::string
& payload_hash
) const
905 const auto string_to_sign
= string_join_reserve("\n",
906 AWS4_HMAC_SHA256_PAYLOAD_STR
,
909 prev_chunk_signature
,
910 AWS4_EMPTY_PAYLOAD_HASH
,
913 ldout(cct
, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
916 /* new chunk signature */
917 const auto sighex
= buf_to_hex(calc_hmac_sha256(signing_key
,
919 /* FIXME(rzarzynski): std::string here is really unnecessary. */
920 return std::string(sighex
.data(), sighex
.size() - 1);
924 bool AWSv4ComplMulti::is_signature_mismatched()
926 /* The validity of previous chunk can be verified only after getting meta-
927 * data of the next one. */
928 const auto payload_hash
= calc_hash_sha256_restart_stream(&sha256_hash
);
929 const auto calc_signature
= calc_chunk_signature(payload_hash
);
931 if (chunk_meta
.get_signature() != calc_signature
) {
932 ldout(cct
, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
934 ldout(cct
, 20) << "AWSv4ComplMulti: declared signature="
935 << chunk_meta
.get_signature() << dendl
;
936 ldout(cct
, 20) << "AWSv4ComplMulti: calculated signature="
937 << calc_signature
<< dendl
;
941 prev_chunk_signature
= chunk_meta
.get_signature();
946 size_t AWSv4ComplMulti::recv_body(char* const buf
, const size_t buf_max
)
948 /* Buffer stores only parsed stream. Raw values reflect the stream
949 * we're getting from a client. */
952 if (chunk_meta
.is_new_chunk_in_stream(stream_pos
)) {
953 /* Verify signature of the previous chunk. We aren't doing that for new
954 * one as the procedure requires calculation of payload hash. This code
955 * won't be triggered for the last, zero-length chunk. Instead, is will
956 * be checked in the complete() method. */
957 if (stream_pos
>= ChunkMeta::META_MAX_SIZE
&& is_signature_mismatched()) {
958 throw rgw::io::Exception(ERR_SIGNATURE_NO_MATCH
, std::system_category());
961 /* We don't have metadata for this range. This means a new chunk, so we
962 * need to parse a fresh portion of the stream. Let's start. */
963 size_t to_extract
= parsing_buf
.capacity() - parsing_buf
.size();
965 const size_t orig_size
= parsing_buf
.size();
966 parsing_buf
.resize(parsing_buf
.size() + to_extract
);
967 const size_t received
= io_base_t::recv_body(parsing_buf
.data() + orig_size
,
969 parsing_buf
.resize(parsing_buf
.size() - (to_extract
- received
));
974 stream_pos
+= received
;
975 to_extract
-= received
;
976 } while (to_extract
> 0);
979 std::tie(chunk_meta
, consumed
) = \
980 ChunkMeta::create_next(cct
, std::move(chunk_meta
),
981 parsing_buf
.data(), parsing_buf
.size());
983 /* We can drop the bytes consumed during metadata parsing. The remainder
984 * can be chunk's data plus possibly beginning of next chunks' metadata. */
985 parsing_buf
.erase(std::begin(parsing_buf
),
986 std::begin(parsing_buf
) + consumed
);
989 size_t to_extract
= \
990 std::min(chunk_meta
.get_data_size(stream_pos
), buf_max
);
992 /* It's quite probable we have a couple of real data bytes stored together
993 * with meta-data in the parsing_buf. We need to extract them and move to
994 * the final buffer. This is a trade-off between frontend's read overhead
996 if (to_extract
> 0 && parsing_buf
.size() > 0) {
997 const auto data_len
= std::min(to_extract
, parsing_buf
.size());
998 const auto data_end_iter
= std::begin(parsing_buf
) + data_len
;
1000 std::copy(std::begin(parsing_buf
), data_end_iter
, buf
);
1001 parsing_buf
.erase(std::begin(parsing_buf
), data_end_iter
);
1003 calc_hash_sha256_update_stream(sha256_hash
, buf
, data_len
);
1005 to_extract
-= data_len
;
1006 buf_pos
+= data_len
;
1009 /* Now we can do the bulk read directly from RestfulClient without any extra
1011 while (to_extract
> 0) {
1012 const size_t received
= io_base_t::recv_body(buf
+ buf_pos
, to_extract
);
1014 if (received
== 0) {
1018 calc_hash_sha256_update_stream(sha256_hash
, buf
+ buf_pos
, received
);
1020 buf_pos
+= received
;
1021 stream_pos
+= received
;
1022 to_extract
-= received
;
1025 dout(20) << "AWSv4ComplMulti: filled=" << buf_pos
<< dendl
;
1029 void AWSv4ComplMulti::modify_request_state(req_state
* const s_rw
)
1031 const char* const decoded_length
= \
1032 s_rw
->info
.env
->get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH");
1034 if (!decoded_length
) {
1037 s_rw
->length
= decoded_length
;
1038 s_rw
->content_length
= parse_content_length(decoded_length
);
1040 if (s_rw
->content_length
< 0) {
1041 ldout(cct
, 10) << "negative AWSv4's content length, aborting" << dendl
;
1046 /* Install the filter over rgw::io::RestfulClient. */
1047 AWS_AUTHv4_IO(s_rw
)->add_filter(
1048 std::static_pointer_cast
<io_base_t
>(shared_from_this()));
1051 bool AWSv4ComplMulti::complete()
1053 /* Now it's time to verify the signature of the last, zero-length chunk. */
1054 if (is_signature_mismatched()) {
1055 ldout(cct
, 10) << "ERROR: signature of last chunk does not match"
1063 rgw::auth::Completer::cmplptr_t
1064 AWSv4ComplMulti::create(const req_state
* const s
,
1065 boost::string_view date
,
1066 boost::string_view credential_scope
,
1067 boost::string_view seed_signature
,
1068 const boost::optional
<std::string
>& secret_key
)
1071 /* Some external authorizers (like Keystone) aren't fully compliant with
1072 * AWSv4. They do not provide the secret_key which is necessary to handle
1073 * the streamed upload. */
1074 throw -ERR_NOT_IMPLEMENTED
;
1077 const auto signing_key
= \
1078 rgw::auth::s3::get_v4_signing_key(s
->cct
, credential_scope
, *secret_key
);
1080 return std::make_shared
<AWSv4ComplMulti
>(s
,
1082 std::move(credential_scope
),
1083 std::move(seed_signature
),
1087 size_t AWSv4ComplSingle::recv_body(char* const buf
, const size_t max
)
1089 const auto received
= io_base_t::recv_body(buf
, max
);
1090 calc_hash_sha256_update_stream(sha256_hash
, buf
, received
);
1095 void AWSv4ComplSingle::modify_request_state(req_state
* const s_rw
)
1097 /* Install the filter over rgw::io::RestfulClient. */
1098 AWS_AUTHv4_IO(s_rw
)->add_filter(
1099 std::static_pointer_cast
<io_base_t
>(shared_from_this()));
1102 bool AWSv4ComplSingle::complete()
1104 /* The completer is only for the cases where signed payload has been
1105 * requested. It won't be used, for instance, during the query string-based
1106 * authentication. */
1107 const auto payload_hash
= calc_hash_sha256_close_stream(&sha256_hash
);
1109 /* Validate x-amz-sha256 */
1110 if (payload_hash
.compare(expected_request_payload_hash
) == 0) {
1113 ldout(cct
, 10) << "ERROR: x-amz-content-sha256 does not match"
1115 ldout(cct
, 10) << "ERROR: grab_aws4_sha256_hash()="
1116 << payload_hash
<< dendl
;
1117 ldout(cct
, 10) << "ERROR: expected_request_payload_hash="
1118 << expected_request_payload_hash
<< dendl
;
1123 AWSv4ComplSingle::AWSv4ComplSingle(const req_state
* const s
)
1124 : io_base_t(nullptr),
1126 expected_request_payload_hash(get_v4_exp_payload_hash(s
->info
)),
1127 sha256_hash(calc_hash_sha256_open_stream()) {
1130 rgw::auth::Completer::cmplptr_t
1131 AWSv4ComplSingle::create(const req_state
* const s
,
1132 const boost::optional
<std::string
>&)
1134 return std::make_shared
<AWSv4ComplSingle
>(s
);
1137 } /* namespace s3 */
1138 } /* namespace auth */
1139 } /* namespace rgw */