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>
20 #include <boost/algorithm/string/trim_all.hpp>
22 #define dout_context g_ceph_context
23 #define dout_subsys ceph_subsys_rgw
25 static const auto signed_subresources
= {
36 "response-cache-control",
37 "response-content-disposition",
38 "response-content-encoding",
39 "response-content-language",
40 "response-content-type",
53 * ?get the canonical amazon-style header for something?
57 get_canon_amz_hdr(const std::map
<std::string
, std::string
>& meta_map
)
61 for (const auto& kv
: meta_map
) {
62 dest
.append(kv
.first
);
64 dest
.append(kv
.second
);
72 * ?get the canonical representation of the object's location
75 get_canon_resource(const char* const request_uri
,
76 const std::map
<std::string
, std::string
>& sub_resources
)
81 dest
.append(request_uri
);
85 for (const auto& subresource
: signed_subresources
) {
86 const auto iter
= sub_resources
.find(subresource
);
87 if (iter
== std::end(sub_resources
)) {
98 dest
.append(iter
->first
);
99 if (! iter
->second
.empty()) {
101 dest
.append(iter
->second
);
105 dout(10) << "get_canon_resource(): dest=" << dest
<< dendl
;
110 * get the header authentication information required to
111 * compute a request's signature
113 void rgw_create_s3_canonical_header(
114 const char* const method
,
115 const char* const content_md5
,
116 const char* const content_type
,
117 const char* const date
,
118 const std::map
<std::string
, std::string
>& meta_map
,
119 const char* const request_uri
,
120 const std::map
<std::string
, std::string
>& sub_resources
,
121 std::string
& dest_str
)
131 dest
.append(content_md5
);
136 dest
.append(content_type
);
145 dest
.append(get_canon_amz_hdr(meta_map
));
146 dest
.append(get_canon_resource(request_uri
, sub_resources
));
151 static inline bool is_base64_for_content_md5(unsigned char c
) {
152 return (isalnum(c
) || isspace(c
) || (c
== '+') || (c
== '/') || (c
== '='));
156 * get the header authentication information required to
157 * compute a request's signature
159 bool rgw_create_s3_canonical_header(const req_info
& info
,
160 utime_t
* const header_time
,
164 const char* const content_md5
= info
.env
->get("HTTP_CONTENT_MD5");
166 for (const char *p
= content_md5
; *p
; p
++) {
167 if (!is_base64_for_content_md5(*p
)) {
168 dout(0) << "NOTICE: bad content-md5 provided (not base64),"
169 << " aborting request p=" << *p
<< " " << (int)*p
<< dendl
;
175 const char *content_type
= info
.env
->get("CONTENT_TYPE");
179 date
= info
.args
.get("Expires");
181 const char *str
= info
.env
->get("HTTP_X_AMZ_DATE");
182 const char *req_date
= str
;
184 req_date
= info
.env
->get("HTTP_DATE");
186 dout(0) << "NOTICE: missing date for auth header" << dendl
;
194 if (!parse_rfc2616(req_date
, &t
)) {
195 dout(0) << "NOTICE: failed to parse date for auth header" << dendl
;
198 if (t
.tm_year
< 70) {
199 dout(0) << "NOTICE: bad date (predates epoch): " << req_date
<< dendl
;
202 *header_time
= utime_t(internal_timegm(&t
), 0);
206 const auto& meta_map
= info
.x_meta_map
;
207 const auto& sub_resources
= info
.args
.get_sub_resources();
209 std::string request_uri
;
210 if (info
.effective_uri
.empty()) {
211 request_uri
= info
.request_uri
;
213 request_uri
= info
.effective_uri
;
216 rgw_create_s3_canonical_header(info
.method
, content_md5
, content_type
,
217 date
.c_str(), meta_map
, request_uri
.c_str(),
218 sub_resources
, dest
);
227 bool is_time_skew_ok(time_t t
)
229 auto req_tp
= ceph::coarse_real_clock::from_time_t(t
);
230 auto cur_tp
= ceph::coarse_real_clock::now();
232 if (req_tp
< cur_tp
- RGW_AUTH_GRACE
||
233 req_tp
> cur_tp
+ RGW_AUTH_GRACE
) {
234 dout(10) << "NOTICE: request time skew too big." << dendl
;
235 using ceph::operator<<;
236 dout(10) << "req_tp=" << req_tp
<< ", cur_tp=" << cur_tp
<< dendl
;
243 static inline int parse_v4_query_string(const req_info
& info
, /* in */
244 boost::string_view
& credential
, /* out */
245 boost::string_view
& signedheaders
, /* out */
246 boost::string_view
& signature
, /* out */
247 boost::string_view
& date
) /* out */
249 /* auth ships with req params ... */
251 /* look for required params */
252 credential
= info
.args
.get("X-Amz-Credential");
253 if (credential
.size() == 0) {
257 date
= info
.args
.get("X-Amz-Date");
259 if (!parse_iso8601(sview2cstr(date
).data(), &date_t
, nullptr, false)) {
263 boost::string_view expires
= info
.args
.get("X-Amz-Expires");
264 if (expires
.empty()) {
267 /* X-Amz-Expires provides the time period, in seconds, for which
268 the generated presigned URL is valid. The minimum value
269 you can set is 1, and the maximum is 604800 (seven days) */
270 time_t exp
= atoll(expires
.data());
271 if ((exp
< 1) || (exp
> 7*24*60*60)) {
272 dout(10) << "NOTICE: exp out of range, exp = " << exp
<< dendl
;
275 /* handle expiration in epoch time */
276 uint64_t req_sec
= (uint64_t)internal_timegm(&date_t
);
277 uint64_t now
= ceph_clock_now();
278 if (now
>= req_sec
+ exp
) {
279 dout(10) << "NOTICE: now = " << now
<< ", req_sec = " << req_sec
<< ", exp = " << exp
<< dendl
;
283 signedheaders
= info
.args
.get("X-Amz-SignedHeaders");
284 if (signedheaders
.size() == 0) {
288 signature
= info
.args
.get("X-Amz-Signature");
289 if (signature
.size() == 0) {
297 static bool get_next_token(const boost::string_view
& s
,
299 const char* const delims
,
300 boost::string_view
& token
)
302 const size_t start
= s
.find_first_not_of(delims
, pos
);
303 if (start
== boost::string_view::npos
) {
308 size_t end
= s
.find_first_of(delims
, start
);
309 if (end
!= boost::string_view::npos
)
312 pos
= end
= s
.size();
315 token
= s
.substr(start
, end
- start
);
319 template<std::size_t ExpectedStrNum
>
320 boost::container::small_vector
<boost::string_view
, ExpectedStrNum
>
321 get_str_vec(const boost::string_view
& str
, const char* const delims
)
323 boost::container::small_vector
<boost::string_view
, ExpectedStrNum
> str_vec
;
326 boost::string_view token
;
327 while (pos
< str
.size()) {
328 if (get_next_token(str
, pos
, delims
, token
)) {
329 if (token
.size() > 0) {
330 str_vec
.push_back(token
);
338 template<std::size_t ExpectedStrNum
>
339 boost::container::small_vector
<boost::string_view
, ExpectedStrNum
>
340 get_str_vec(const boost::string_view
& str
)
342 const char delims
[] = ";,= \t";
343 return get_str_vec
<ExpectedStrNum
>(str
, delims
);
347 static inline int parse_v4_auth_header(const req_info
& info
, /* in */
348 boost::string_view
& credential
, /* out */
349 boost::string_view
& signedheaders
, /* out */
350 boost::string_view
& signature
, /* out */
351 boost::string_view
& date
) /* out */
353 boost::string_view
input(info
.env
->get("HTTP_AUTHORIZATION", ""));
355 input
= input
.substr(::strlen(AWS4_HMAC_SHA256_STR
) + 1);
356 } catch (std::out_of_range
&) {
357 /* We should never ever run into this situation as the presence of
358 * AWS4_HMAC_SHA256_STR had been verified earlier. */
359 dout(10) << "credentials string is too short" << dendl
;
363 std::map
<boost::string_view
, boost::string_view
> kv
;
364 for (const auto& s
: get_str_vec
<4>(input
, ",")) {
365 const auto parsed_pair
= parse_key_value(s
);
367 kv
[parsed_pair
->first
] = parsed_pair
->second
;
369 dout(10) << "NOTICE: failed to parse auth header (s=" << s
<< ")"
375 static const std::array
<boost::string_view
, 3> required_keys
= {
381 /* Ensure that the presigned required keys are really there. */
382 for (const auto& k
: required_keys
) {
383 if (kv
.find(k
) == std::end(kv
)) {
384 dout(10) << "NOTICE: auth header missing key: " << k
<< dendl
;
389 credential
= kv
["Credential"];
390 signedheaders
= kv
["SignedHeaders"];
391 signature
= kv
["Signature"];
394 dout(10) << "v4 signature format = " << signature
<< dendl
;
396 /* ------------------------- handle x-amz-date header */
400 const char *d
= info
.env
->get("HTTP_X_AMZ_DATE");
402 if (!parse_iso8601(d
, &t
, NULL
, false)) {
403 dout(10) << "error reading date via http_x_amz_date" << dendl
;
408 if (!is_time_skew_ok(internal_timegm(&t
))) {
409 return -ERR_REQUEST_TIME_SKEWED
;
415 int parse_credentials(const req_info
& info
, /* in */
416 boost::string_view
& access_key_id
, /* out */
417 boost::string_view
& credential_scope
, /* out */
418 boost::string_view
& signedheaders
, /* out */
419 boost::string_view
& signature
, /* out */
420 boost::string_view
& date
, /* out */
421 bool& using_qs
) /* out */
423 const char* const http_auth
= info
.env
->get("HTTP_AUTHORIZATION");
424 using_qs
= http_auth
== nullptr || http_auth
[0] == '\0';
427 boost::string_view credential
;
429 ret
= parse_v4_query_string(info
, credential
, signedheaders
,
432 ret
= parse_v4_auth_header(info
, credential
, signedheaders
,
440 /* AKIAIVKTAZLOCF43WNQD/AAAAMMDD/region/host/aws4_request */
441 dout(10) << "v4 credential format = " << credential
<< dendl
;
443 if (std::count(credential
.begin(), credential
.end(), '/') != 4) {
447 /* credential must end with 'aws4_request' */
448 if (credential
.find("aws4_request") == std::string::npos
) {
452 /* grab access key id */
453 const size_t pos
= credential
.find("/");
454 access_key_id
= credential
.substr(0, pos
);
455 dout(10) << "access key id = " << access_key_id
<< dendl
;
457 /* grab credential scope */
458 credential_scope
= credential
.substr(pos
+ 1);
459 dout(10) << "credential scope = " << credential_scope
<< dendl
;
464 static inline bool char_needs_aws4_escaping(const char c
)
466 if ((c
>= 'a' && c
<= 'z') ||
467 (c
>= 'A' && c
<= 'Z') ||
468 (c
>= '0' && c
<= '9')) {
482 static inline std::string
aws4_uri_encode(const std::string
& src
)
486 for (const std::string::value_type c
: src
) {
487 if (char_needs_aws4_escaping(c
)) {
488 rgw_uri_escape_char(c
, result
);
497 static inline std::string
aws4_uri_recode(const boost::string_view
& src
)
499 std::string decoded
= url_decode(src
);
500 return aws4_uri_encode(decoded
);
503 std::string
get_v4_canonical_qs(const req_info
& info
, const bool using_qs
)
505 const std::string
*params
= &info
.request_params
;
506 std::string copy_params
;
507 if (params
->empty()) {
508 /* Optimize the typical flow. */
509 return std::string();
511 if (params
->find_first_of('+') != std::string::npos
) {
512 copy_params
= *params
;
513 boost::replace_all(copy_params
, "+", " ");
514 params
= ©_params
;
517 /* Handle case when query string exists. Step 3 described in: http://docs.
518 * aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html */
519 std::map
<std::string
, std::string
> canonical_qs_map
;
520 for (const auto& s
: get_str_vec
<5>(*params
, "&")) {
521 boost::string_view key
, val
;
522 const auto parsed_pair
= parse_key_value(s
);
524 std::tie(key
, val
) = *parsed_pair
;
526 /* Handling a parameter without any value (even the empty one). That's
527 * it, we've encountered something like "this_param&other_param=val"
528 * which is used by S3 for subresources. */
532 if (using_qs
&& key
== "X-Amz-Signature") {
533 /* Preserving the original behaviour of get_v4_canonical_qs() here. */
537 if (key
== "X-Amz-Credential") {
538 /* FIXME(rzarzynski): I can't find any comment in the previously linked
539 * Amazon's docs saying that X-Amz-Credential should be handled in this
541 canonical_qs_map
[key
.to_string()] = val
.to_string();
543 canonical_qs_map
[aws4_uri_recode(key
)] = aws4_uri_recode(val
);
547 /* Thanks to the early exist we have the guarantee that canonical_qs_map has
548 * at least one element. */
549 auto iter
= std::begin(canonical_qs_map
);
550 std::string canonical_qs
;
551 canonical_qs
.append(iter
->first
)
552 .append("=", ::strlen("="))
553 .append(iter
->second
);
555 for (iter
++; iter
!= std::end(canonical_qs_map
); iter
++) {
556 canonical_qs
.append("&", ::strlen("&"))
558 .append("=", ::strlen("="))
559 .append(iter
->second
);
565 boost::optional
<std::string
>
566 get_v4_canonical_headers(const req_info
& info
,
567 const boost::string_view
& signedheaders
,
569 const bool force_boto2_compat
)
571 std::map
<boost::string_view
, std::string
> canonical_hdrs_map
;
572 for (const auto& token
: get_str_vec
<5>(signedheaders
, ";")) {
573 /* TODO(rzarzynski): we'd like to switch to sstring here but it should
574 * get push_back() and reserve() first. */
575 std::string token_env
= "HTTP_";
576 token_env
.reserve(token
.length() + std::strlen("HTTP_") + 1);
578 std::transform(std::begin(token
), std::end(token
),
579 std::back_inserter(token_env
), [](const int c
) {
580 return c
== '-' ? '_' : std::toupper(c
);
583 if (token_env
== "HTTP_CONTENT_LENGTH") {
584 token_env
= "CONTENT_LENGTH";
585 } else if (token_env
== "HTTP_CONTENT_TYPE") {
586 token_env
= "CONTENT_TYPE";
588 const char* const t
= info
.env
->get(token_env
.c_str());
590 dout(10) << "warning env var not available" << dendl
;
594 std::string
token_value(t
);
595 if (token_env
== "HTTP_CONTENT_MD5" &&
596 !std::all_of(std::begin(token_value
), std::end(token_value
),
597 is_base64_for_content_md5
)) {
598 dout(0) << "NOTICE: bad content-md5 provided (not base64)"
599 << ", aborting request" << dendl
;
603 if (force_boto2_compat
&& using_qs
&& token
== "host") {
604 boost::string_view port
= info
.env
->get("SERVER_PORT", "");
605 boost::string_view secure_port
= info
.env
->get("SERVER_PORT_SECURE", "");
607 if (!secure_port
.empty()) {
608 if (secure_port
!= "443")
609 token_value
.append(":", std::strlen(":"))
610 .append(secure_port
.data(), secure_port
.length());
611 } else if (!port
.empty()) {
613 token_value
.append(":", std::strlen(":"))
614 .append(port
.data(), port
.length());
618 canonical_hdrs_map
[token
] = rgw_trim_whitespace(token_value
);
621 std::string canonical_hdrs
;
622 for (const auto& header
: canonical_hdrs_map
) {
623 const boost::string_view
& name
= header
.first
;
624 std::string value
= header
.second
;
625 boost::trim_all
<std::string
>(value
);
627 canonical_hdrs
.append(name
.data(), name
.length())
628 .append(":", std::strlen(":"))
630 .append("\n", std::strlen("\n"));
633 return canonical_hdrs
;
637 * create canonical request for signature version 4
639 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
642 get_v4_canon_req_hash(CephContext
* cct
,
643 const boost::string_view
& http_verb
,
644 const std::string
& canonical_uri
,
645 const std::string
& canonical_qs
,
646 const std::string
& canonical_hdrs
,
647 const boost::string_view
& signed_hdrs
,
648 const boost::string_view
& request_payload_hash
)
650 ldout(cct
, 10) << "payload request hash = " << request_payload_hash
<< dendl
;
652 const auto canonical_req
= string_join_reserve("\n",
658 request_payload_hash
);
660 const auto canonical_req_hash
= calc_hash_sha256(canonical_req
);
662 ldout(cct
, 10) << "canonical request = " << canonical_req
<< dendl
;
663 ldout(cct
, 10) << "canonical request hash = "
664 << buf_to_hex(canonical_req_hash
).data() << dendl
;
666 return canonical_req_hash
;
670 * create string to sign for signature version 4
672 * http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
674 AWSEngine::VersionAbstractor::string_to_sign_t
675 get_v4_string_to_sign(CephContext
* const cct
,
676 const boost::string_view
& algorithm
,
677 const boost::string_view
& request_date
,
678 const boost::string_view
& credential_scope
,
679 const sha256_digest_t
& canonreq_hash
)
681 const auto hexed_cr_hash
= buf_to_hex(canonreq_hash
);
682 const boost::string_view
hexed_cr_hash_str(hexed_cr_hash
.data(),
683 hexed_cr_hash
.size() - 1);
685 const auto string_to_sign
= string_join_reserve("\n",
691 ldout(cct
, 10) << "string to sign = "
692 << rgw::crypt_sanitize::log_content
{string_to_sign
}
695 return string_to_sign
;
699 static inline std::tuple
<boost::string_view
, /* date */
700 boost::string_view
, /* region */
701 boost::string_view
> /* service */
702 parse_cred_scope(boost::string_view credential_scope
)
705 size_t pos
= credential_scope
.find("/");
706 const auto date_cs
= credential_scope
.substr(0, pos
);
707 credential_scope
= credential_scope
.substr(pos
+ 1);
710 pos
= credential_scope
.find("/");
711 const auto region_cs
= credential_scope
.substr(0, pos
);
712 credential_scope
= credential_scope
.substr(pos
+ 1);
715 pos
= credential_scope
.find("/");
716 const auto service_cs
= credential_scope
.substr(0, pos
);
718 return std::make_tuple(date_cs
, region_cs
, service_cs
);
721 static inline std::vector
<unsigned char>
722 transform_secret_key(const boost::string_view
& secret_access_key
)
724 /* TODO(rzarzynski): switch to constexpr when C++14 becomes available. */
725 static const std::initializer_list
<unsigned char> AWS4
{ 'A', 'W', 'S', '4' };
727 /* boost::container::small_vector might be used here if someone wants to
728 * optimize out even more dynamic allocations. */
729 std::vector
<unsigned char> secret_key_utf8
;
730 secret_key_utf8
.reserve(AWS4
.size() + secret_access_key
.size());
731 secret_key_utf8
.assign(AWS4
);
733 for (const auto c
: secret_access_key
) {
734 std::array
<unsigned char, MAX_UTF8_SZ
> buf
;
735 const size_t n
= encode_utf8(c
, buf
.data());
736 secret_key_utf8
.insert(std::end(secret_key_utf8
),
737 std::begin(buf
), std::begin(buf
) + n
);
740 return secret_key_utf8
;
744 * calculate the SigningKey of AWS auth version 4
746 static sha256_digest_t
747 get_v4_signing_key(CephContext
* const cct
,
748 const boost::string_view
& credential_scope
,
749 const boost::string_view
& secret_access_key
)
751 boost::string_view date
, region
, service
;
752 std::tie(date
, region
, service
) = parse_cred_scope(credential_scope
);
754 const auto utfed_sec_key
= transform_secret_key(secret_access_key
);
755 const auto date_k
= calc_hmac_sha256(utfed_sec_key
, date
);
756 const auto region_k
= calc_hmac_sha256(date_k
, region
);
757 const auto service_k
= calc_hmac_sha256(region_k
, service
);
760 const auto signing_key
= calc_hmac_sha256(service_k
,
761 boost::string_view("aws4_request"));
763 ldout(cct
, 10) << "date_k = " << buf_to_hex(date_k
).data() << dendl
;
764 ldout(cct
, 10) << "region_k = " << buf_to_hex(region_k
).data() << dendl
;
765 ldout(cct
, 10) << "service_k = " << buf_to_hex(service_k
).data() << dendl
;
766 ldout(cct
, 10) << "signing_k = " << buf_to_hex(signing_key
).data() << dendl
;
772 * calculate the AWS signature version 4
774 * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
776 * srv_signature_t is an alias over Ceph's basic_sstring. We're using
777 * it to keep everything within the stack boundaries instead of doing
778 * dynamic allocations.
780 AWSEngine::VersionAbstractor::server_signature_t
781 get_v4_signature(const boost::string_view
& credential_scope
,
782 CephContext
* const cct
,
783 const boost::string_view
& secret_key
,
784 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
)
786 auto signing_key
= get_v4_signing_key(cct
, credential_scope
, secret_key
);
788 /* The server-side generated digest for comparison. */
789 const auto digest
= calc_hmac_sha256(signing_key
, string_to_sign
);
791 /* TODO(rzarzynski): I would love to see our sstring having reserve() and
792 * the non-const data() variant like C++17's std::string. */
793 using srv_signature_t
= AWSEngine::VersionAbstractor::server_signature_t
;
794 srv_signature_t
signature(srv_signature_t::initialized_later(),
796 buf_to_hex(digest
.data(), digest
.size(), signature
.begin());
798 ldout(cct
, 10) << "generated signature = " << signature
<< dendl
;
803 AWSEngine::VersionAbstractor::server_signature_t
804 get_v2_signature(CephContext
* const cct
,
805 const std::string
& secret_key
,
806 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
)
808 if (secret_key
.empty()) {
812 const auto digest
= calc_hmac_sha1(secret_key
, string_to_sign
);
814 /* 64 is really enough */;
816 const int ret
= ceph_armor(std::begin(buf
),
817 std::begin(buf
) + 64,
819 std::begin(digest
) + digest
.size());
821 ldout(cct
, 10) << "ceph_armor failed" << dendl
;
825 using srv_signature_t
= AWSEngine::VersionAbstractor::server_signature_t
;
826 return srv_signature_t(buf
, ret
);
830 bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos
) const
832 return stream_pos
>= (data_offset_in_stream
+ data_length
);
835 size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos
) const
837 if (stream_pos
> (data_offset_in_stream
+ data_length
)) {
838 /* Data in parsing_buf. */
841 return data_offset_in_stream
+ data_length
- stream_pos
;
846 /* AWSv4 completers begin. */
847 std::pair
<AWSv4ComplMulti::ChunkMeta
, size_t /* consumed */>
848 AWSv4ComplMulti::ChunkMeta::create_next(CephContext
* const cct
,
850 const char* const metabuf
,
851 const size_t metabuf_len
)
853 boost::string_ref
metastr(metabuf
, metabuf_len
);
855 const size_t semicolon_pos
= metastr
.find(";");
856 if (semicolon_pos
== boost::string_ref::npos
) {
857 ldout(cct
, 20) << "AWSv4ComplMulti cannot find the ';' separator"
859 throw rgw::io::Exception(EINVAL
, std::system_category());
862 char* data_field_end
;
863 /* strtoull ignores the "\r\n" sequence after each non-first chunk. */
864 const size_t data_length
= std::strtoull(metabuf
, &data_field_end
, 16);
865 if (data_length
== 0 && data_field_end
== metabuf
) {
866 ldout(cct
, 20) << "AWSv4ComplMulti: cannot parse the data size"
868 throw rgw::io::Exception(EINVAL
, std::system_category());
871 /* Parse the chunk_signature=... part. */
872 const auto signature_part
= metastr
.substr(semicolon_pos
+ 1);
873 const size_t eq_sign_pos
= signature_part
.find("=");
874 if (eq_sign_pos
== boost::string_ref::npos
) {
875 ldout(cct
, 20) << "AWSv4ComplMulti: cannot find the '=' separator"
877 throw rgw::io::Exception(EINVAL
, std::system_category());
880 /* OK, we have at least the beginning of a signature. */
881 const size_t data_sep_pos
= signature_part
.find("\r\n");
882 if (data_sep_pos
== boost::string_ref::npos
) {
883 ldout(cct
, 20) << "AWSv4ComplMulti: no new line at signature end"
885 throw rgw::io::Exception(EINVAL
, std::system_category());
888 const auto signature
= \
889 signature_part
.substr(eq_sign_pos
+ 1, data_sep_pos
- 1 - eq_sign_pos
);
890 if (signature
.length() != SIG_SIZE
) {
891 ldout(cct
, 20) << "AWSv4ComplMulti: signature.length() != 64"
893 throw rgw::io::Exception(EINVAL
, std::system_category());
896 const size_t data_starts_in_stream
= \
897 + semicolon_pos
+ strlen(";") + data_sep_pos
+ strlen("\r\n")
898 + old
.data_offset_in_stream
+ old
.data_length
;
900 ldout(cct
, 20) << "parsed new chunk; signature=" << signature
901 << ", data_length=" << data_length
902 << ", data_starts_in_stream=" << data_starts_in_stream
905 return std::make_pair(ChunkMeta(data_starts_in_stream
,
912 AWSv4ComplMulti::calc_chunk_signature(const std::string
& payload_hash
) const
914 const auto string_to_sign
= string_join_reserve("\n",
915 AWS4_HMAC_SHA256_PAYLOAD_STR
,
918 prev_chunk_signature
,
919 AWS4_EMPTY_PAYLOAD_HASH
,
922 ldout(cct
, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
925 /* new chunk signature */
926 const auto sighex
= buf_to_hex(calc_hmac_sha256(signing_key
,
928 /* FIXME(rzarzynski): std::string here is really unnecessary. */
929 return std::string(sighex
.data(), sighex
.size() - 1);
933 bool AWSv4ComplMulti::is_signature_mismatched()
935 /* The validity of previous chunk can be verified only after getting meta-
936 * data of the next one. */
937 const auto payload_hash
= calc_hash_sha256_restart_stream(&sha256_hash
);
938 const auto calc_signature
= calc_chunk_signature(payload_hash
);
940 if (chunk_meta
.get_signature() != calc_signature
) {
941 ldout(cct
, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
943 ldout(cct
, 20) << "AWSv4ComplMulti: declared signature="
944 << chunk_meta
.get_signature() << dendl
;
945 ldout(cct
, 20) << "AWSv4ComplMulti: calculated signature="
946 << calc_signature
<< dendl
;
950 prev_chunk_signature
= chunk_meta
.get_signature();
955 size_t AWSv4ComplMulti::recv_body(char* const buf
, const size_t buf_max
)
957 /* Buffer stores only parsed stream. Raw values reflect the stream
958 * we're getting from a client. */
961 if (chunk_meta
.is_new_chunk_in_stream(stream_pos
)) {
962 /* Verify signature of the previous chunk. We aren't doing that for new
963 * one as the procedure requires calculation of payload hash. This code
964 * won't be triggered for the last, zero-length chunk. Instead, is will
965 * be checked in the complete() method. */
966 if (stream_pos
>= ChunkMeta::META_MAX_SIZE
&& is_signature_mismatched()) {
967 throw rgw::io::Exception(ERR_SIGNATURE_NO_MATCH
, std::system_category());
970 /* We don't have metadata for this range. This means a new chunk, so we
971 * need to parse a fresh portion of the stream. Let's start. */
972 size_t to_extract
= parsing_buf
.capacity() - parsing_buf
.size();
974 const size_t orig_size
= parsing_buf
.size();
975 parsing_buf
.resize(parsing_buf
.size() + to_extract
);
976 const size_t received
= io_base_t::recv_body(parsing_buf
.data() + orig_size
,
978 parsing_buf
.resize(parsing_buf
.size() - (to_extract
- received
));
983 stream_pos
+= received
;
984 to_extract
-= received
;
985 } while (to_extract
> 0);
988 std::tie(chunk_meta
, consumed
) = \
989 ChunkMeta::create_next(cct
, std::move(chunk_meta
),
990 parsing_buf
.data(), parsing_buf
.size());
992 /* We can drop the bytes consumed during metadata parsing. The remainder
993 * can be chunk's data plus possibly beginning of next chunks' metadata. */
994 parsing_buf
.erase(std::begin(parsing_buf
),
995 std::begin(parsing_buf
) + consumed
);
998 size_t stream_pos_was
= stream_pos
- parsing_buf
.size();
1000 size_t to_extract
= \
1001 std::min(chunk_meta
.get_data_size(stream_pos_was
), buf_max
);
1002 dout(30) << "AWSv4ComplMulti: stream_pos_was=" << stream_pos_was
<< ", to_extract=" << to_extract
<< dendl
;
1004 /* It's quite probable we have a couple of real data bytes stored together
1005 * with meta-data in the parsing_buf. We need to extract them and move to
1006 * the final buffer. This is a trade-off between frontend's read overhead
1008 if (to_extract
> 0 && parsing_buf
.size() > 0) {
1009 const auto data_len
= std::min(to_extract
, parsing_buf
.size());
1010 const auto data_end_iter
= std::begin(parsing_buf
) + data_len
;
1011 dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract
<< ", data_len=" << data_len
<< dendl
;
1013 std::copy(std::begin(parsing_buf
), data_end_iter
, buf
);
1014 parsing_buf
.erase(std::begin(parsing_buf
), data_end_iter
);
1016 calc_hash_sha256_update_stream(sha256_hash
, buf
, data_len
);
1018 to_extract
-= data_len
;
1019 buf_pos
+= data_len
;
1022 /* Now we can do the bulk read directly from RestfulClient without any extra
1024 while (to_extract
> 0) {
1025 const size_t received
= io_base_t::recv_body(buf
+ buf_pos
, to_extract
);
1026 dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract
<< ", received=" << received
<< dendl
;
1028 if (received
== 0) {
1032 calc_hash_sha256_update_stream(sha256_hash
, buf
+ buf_pos
, received
);
1034 buf_pos
+= received
;
1035 stream_pos
+= received
;
1036 to_extract
-= received
;
1039 dout(20) << "AWSv4ComplMulti: filled=" << buf_pos
<< dendl
;
1043 void AWSv4ComplMulti::modify_request_state(req_state
* const s_rw
)
1045 const char* const decoded_length
= \
1046 s_rw
->info
.env
->get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH");
1048 if (!decoded_length
) {
1051 s_rw
->length
= decoded_length
;
1052 s_rw
->content_length
= parse_content_length(decoded_length
);
1054 if (s_rw
->content_length
< 0) {
1055 ldout(cct
, 10) << "negative AWSv4's content length, aborting" << dendl
;
1060 /* Install the filter over rgw::io::RestfulClient. */
1061 AWS_AUTHv4_IO(s_rw
)->add_filter(
1062 std::static_pointer_cast
<io_base_t
>(shared_from_this()));
1065 bool AWSv4ComplMulti::complete()
1067 /* Now it's time to verify the signature of the last, zero-length chunk. */
1068 if (is_signature_mismatched()) {
1069 ldout(cct
, 10) << "ERROR: signature of last chunk does not match"
1077 rgw::auth::Completer::cmplptr_t
1078 AWSv4ComplMulti::create(const req_state
* const s
,
1079 boost::string_view date
,
1080 boost::string_view credential_scope
,
1081 boost::string_view seed_signature
,
1082 const boost::optional
<std::string
>& secret_key
)
1085 /* Some external authorizers (like Keystone) aren't fully compliant with
1086 * AWSv4. They do not provide the secret_key which is necessary to handle
1087 * the streamed upload. */
1088 throw -ERR_NOT_IMPLEMENTED
;
1091 const auto signing_key
= \
1092 rgw::auth::s3::get_v4_signing_key(s
->cct
, credential_scope
, *secret_key
);
1094 return std::make_shared
<AWSv4ComplMulti
>(s
,
1096 std::move(credential_scope
),
1097 std::move(seed_signature
),
1101 size_t AWSv4ComplSingle::recv_body(char* const buf
, const size_t max
)
1103 const auto received
= io_base_t::recv_body(buf
, max
);
1104 calc_hash_sha256_update_stream(sha256_hash
, buf
, received
);
1109 void AWSv4ComplSingle::modify_request_state(req_state
* const s_rw
)
1111 /* Install the filter over rgw::io::RestfulClient. */
1112 AWS_AUTHv4_IO(s_rw
)->add_filter(
1113 std::static_pointer_cast
<io_base_t
>(shared_from_this()));
1116 bool AWSv4ComplSingle::complete()
1118 /* The completer is only for the cases where signed payload has been
1119 * requested. It won't be used, for instance, during the query string-based
1120 * authentication. */
1121 const auto payload_hash
= calc_hash_sha256_close_stream(&sha256_hash
);
1123 /* Validate x-amz-sha256 */
1124 if (payload_hash
.compare(expected_request_payload_hash
) == 0) {
1127 ldout(cct
, 10) << "ERROR: x-amz-content-sha256 does not match"
1129 ldout(cct
, 10) << "ERROR: grab_aws4_sha256_hash()="
1130 << payload_hash
<< dendl
;
1131 ldout(cct
, 10) << "ERROR: expected_request_payload_hash="
1132 << expected_request_payload_hash
<< dendl
;
1137 AWSv4ComplSingle::AWSv4ComplSingle(const req_state
* const s
)
1138 : io_base_t(nullptr),
1140 expected_request_payload_hash(get_v4_exp_payload_hash(s
->info
)),
1141 sha256_hash(calc_hash_sha256_open_stream()) {
1144 rgw::auth::Completer::cmplptr_t
1145 AWSv4ComplSingle::create(const req_state
* const s
,
1146 const boost::optional
<std::string
>&)
1148 return std::make_shared
<AWSv4ComplSingle
>(s
);
1151 } /* namespace s3 */
1152 } /* namespace auth */
1153 } /* namespace rgw */