]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: cumulatively fix 6 AWS SigV4 request failure cases
authorMatt Benjamin <mbenjamin@redhat.com>
Mon, 27 Nov 2023 17:00:43 +0000 (12:00 -0500)
committerCasey Bodley <cbodley@redhat.com>
Tue, 13 Feb 2024 18:28:30 +0000 (13:28 -0500)
These changes address checksum header identification and signing
algorithm selection, including checksum trailer verification
for signed- and unsigned-payload cases.

These changes address all the actual S3 request failures I have
so far been able to reproduce, with and without content checksums
and/or new trailing checksum headers, and with and without
SSL.

Fixes: https://tracker.ceph.com/issues/63153
Specifically, it fixes the request failures that motivated the
initial tracker filing.  It extracts but does not validate new client
content checksums if present.  Validation and management of new
S3 content-checksum headers will follow in a subsequent change.

Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
squashed commits:

* wip chunk meta parsing--seem to have first AWSv4ComplMulti::ChunkMeta::create_next sort of parsing
* use constexpr sarlen(...) for static array lengths throughout rgw_auth_s3.cc
* link AWSv4CompleMulti::ChunkMeta to its enclosing completer
* capture original content-length header before AWSv4ComplMulti overwrites it
* mostly extract the trailer
* fix misordered content-length, experiment w/exbuf
* save leftover bytes between calls to AWSv4ComplMulti::recv_chunk()
* propagate data_offset_in_stream from AWSv4ComplMulti::recv_chunk()
* clean up trailer section extract
* trailer section cleanup and introduce extract_helper
* unrolled checksum extract--fixup
* fix sv_trailer end pos, and cleanup
* add proplist interface to rgw::auth::Completer and AWSv4ComplMulti
* spliterate trailers
* check completer props
* redefine prop_map to point into already-allocated trailer_vec
* hax: thread a counter onto AWSv4ComplMulti recv_body() and recv_chunk path
* fix apparent bug where due to reads less than chunk_size induce a final, zero-length read that was skipped before forcing recognition of the last chunk in the stream
* check only for a trailing checksum named in x-amz-trailer
* don't try to match signatures when no signature provided (because streaming unsigned)
* oops, fix content_length decl
* fix recognition of next chunk envelope in unsigned aws-chunk case
* clean up AWSv4CompMulti flags and correctly detect aws unsigned chunked
* rework checksum-trailer extraction and introduce AWSv4ComplMulti::calc_v4_trailing_signature
* thread const struct req_state* into AWSv4ComplMulti
* large cleanup of trailer parsing, no regression
* fix trailer signature calculation--checks
* correctly generate final chunk hmac
* typo in comment
* verify trailing signature when expected (using expected final chunk signature)
* move trailer_vec back onto recv_body()'s stack
* remove strange completer comment
* remove last_frag (now points into parsing_buf)
* remove implied dependency on content_length
* move trailer recognition to AWSv4ComplMulti::complete()
* remove now-unused is_last_chunk() predicate
* remove unused ChunkMeta::completer
* responses to review comments
* when trailer is sig expected, fail (only) if none present or if it does not match calculated
* remove stale parse_content_length(...) decl
* remove now-unused AWSv4ComplMulti::content_length
* fix extract_helper end search position as in mut_extract_helper
* change "\n" reserve term in get_canon_amz_hdrs() part of the sum (review)
    and initialize length to 0
* remove debugging code

Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
src/rgw/rgw_auth.h
src/rgw/rgw_auth_s3.cc
src/rgw/rgw_auth_s3.h
src/rgw/rgw_common.h
src/rgw/rgw_file_int.h
src/rgw/rgw_rest.cc
src/rgw/rgw_rest.h
src/rgw/rgw_rest_s3.cc

index 2668af3a3c1d8d738671b2e2307cff6e4befe2b2..e2de0afb72655ad7be1368abab7e5960ae4f326f 100644 (file)
@@ -10,6 +10,8 @@
 #include <system_error>
 #include <utility>
 
+#include "include/function2.hpp"
+
 #include "rgw_common.h"
 #include "rgw_web_idp.h"
 
index 6f0141ef9415835dd6e57942f5edcbdca9fded7b..765d19bfa6899a2365ac711d5a6c106edb096efa 100644 (file)
@@ -2,6 +2,7 @@
 // vim: ts=8 sw=2 smarttab ft=cpp
 
 #include <algorithm>
+#include <boost/algorithm/string/predicate.hpp>
 #include <map>
 #include <iterator>
 #include <string>
@@ -10,6 +11,7 @@
 
 #include "common/armor.h"
 #include "common/utf8.h"
+#include "common/split.h"
 #include "rgw_rest_s3.h"
 #include "rgw_auth_s3.h"
 #include "rgw_common.h"
@@ -61,12 +63,19 @@ static const auto signed_subresources = {
  * ?get the canonical amazon-style header for something?
  */
 
+template<typename M>
 static std::string
-get_canon_amz_hdr(const meta_map_t& meta_map)
+get_canon_amz_hdrs(const M& map)
 {
-  std::string dest;
-
-  for (const auto& kv : meta_map) {
+  size_t length = 0;
+  std::string dest; // why dest?
+  std::for_each(map.begin(), map.end(), [&length] (const auto& elt) -> void {
+    length += elt.first.size() + sarrlen(":") + elt.second.size() +
+      sarrlen("\n");
+  });
+  dest.reserve(length);
+
+  for (const auto& kv : map) {
     dest.append(kv.first);
     dest.append(":");
     dest.append(kv.second);
@@ -74,7 +83,7 @@ get_canon_amz_hdr(const meta_map_t& meta_map)
   }
 
   return dest;
-}
+} /* get_canon_amz_hdrs */
 
 /*
  * ?get the canonical representation of the object's location
@@ -152,8 +161,8 @@ void rgw_create_s3_canonical_header(
   }
   dest.append("\n");
 
-  dest.append(get_canon_amz_hdr(meta_map));
-  dest.append(get_canon_amz_hdr(qs_map));
+  dest.append(get_canon_amz_hdrs(meta_map));
+  dest.append(get_canon_amz_hdrs(qs_map));
   dest.append(get_canon_resource(dpp, request_uri, sub_resources));
 
   dest_str = dest;
@@ -604,13 +613,13 @@ std::string get_v4_canonical_qs(const req_info& info, const bool using_qs)
   auto iter = std::begin(canonical_qs_map);
   std::string canonical_qs;
   canonical_qs.append(iter->first)
-              .append("=", ::strlen("="))
+              .append("=", sarrlen("="))
               .append(iter->second);
 
   for (iter++; iter != std::end(canonical_qs_map); iter++) {
-    canonical_qs.append("&", ::strlen("&"))
+    canonical_qs.append("&", sarrlen("&"))
                 .append(iter->first)
-                .append("=", ::strlen("="))
+                .append("=", sarrlen("="))
                 .append(iter->second);
   }
 
@@ -647,13 +656,13 @@ std::string gen_v4_canonical_qs(const req_info& info, bool is_non_s3_op)
   auto iter = std::begin(canonical_qs_map);
   std::string canonical_qs;
   canonical_qs.append(iter->first)
-              .append("=", ::strlen("="))
+              .append("=", sarrlen("="))
               .append(iter->second);
 
   for (iter++; iter != std::end(canonical_qs_map); iter++) {
-    canonical_qs.append("&", ::strlen("&"))
+    canonical_qs.append("&", sarrlen("&"))
                 .append(iter->first)
-                .append("=", ::strlen("="))
+                .append("=", sarrlen("="))
                 .append(iter->second);
   }
 
@@ -700,8 +709,9 @@ get_v4_canonical_headers(const req_info& info,
     /* TODO(rzarzynski): we'd like to switch to sstring here but it should
      * get push_back() and reserve() first. */
     std::string token_env = "HTTP_";
-    token_env.reserve(token.length() + std::strlen("HTTP_") + 1);
+    token_env.reserve(token.length() + sarrlen("HTTP_") + 1);
 
+    /* XXX can we please stop doing this? */
     std::transform(std::begin(token), std::end(token),
                    std::back_inserter(token_env), [](const int c) {
                      return c == '-' ? '_' : c == '_' ? '-' : std::toupper(c);
@@ -733,11 +743,11 @@ get_v4_canonical_headers(const req_info& info,
 
       if (!secure_port.empty()) {
        if (secure_port != "443")
-         token_value.append(":", std::strlen(":"))
+         token_value.append(":", sarrlen(":"))
                      .append(secure_port.data(), secure_port.length());
       } else if (!port.empty()) {
        if (port != "80")
-         token_value.append(":", std::strlen(":"))
+         token_value.append(":", sarrlen(":"))
                      .append(port.data(), port.length());
       }
     }
@@ -752,9 +762,9 @@ get_v4_canonical_headers(const req_info& info,
     boost::trim_all<std::string>(value);
 
     canonical_hdrs.append(name.data(), name.length())
-                  .append(":", std::strlen(":"))
+                  .append(":", sarrlen(":"))
                   .append(value)
-                  .append("\n", std::strlen("\n"));
+                  .append("\n", sarrlen("\n"));
   }
   return canonical_hdrs;
 }
@@ -812,9 +822,9 @@ std::string gen_v4_canonical_headers(const req_info& info,
     signed_hdrs->append(name);
 
     canonical_hdrs.append(name.data(), name.length())
-                  .append(":", std::strlen(":"))
+                  .append(":", sarrlen(":"))
                   .append(value)
-                  .append("\n", std::strlen("\n"));
+                  .append("\n", sarrlen("\n"));
   }
 
   return canonical_hdrs;
@@ -1021,7 +1031,7 @@ get_v2_signature(CephContext* const cct,
 bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos) const
 {
   return stream_pos >= (data_offset_in_stream + data_length);
-}
+} /* ChunkMeta::is_new_chunk_in_stream */
 
 size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos) const
 {
@@ -1031,24 +1041,22 @@ size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos) const
   } else {
     return data_offset_in_stream + data_length - stream_pos;
   }
-}
-
+} /* ChunkMeta::get_data_size */
 
 /* AWSv4 completers begin. */
 std::pair<AWSv4ComplMulti::ChunkMeta, size_t /* consumed */>
 AWSv4ComplMulti::ChunkMeta::create_next(CephContext* const cct,
                                         ChunkMeta&& old,
                                         const char* const metabuf,
-                                        const size_t metabuf_len)
+                                        const size_t metabuf_len,
+                                       uint32_t flags)
 {
   std::string_view metastr(metabuf, metabuf_len);
 
-  const size_t semicolon_pos = metastr.find(";");
-  if (semicolon_pos == std::string_view::npos) {
-    ldout(cct, 20) << "AWSv4ComplMulti cannot find the ';' separator"
-                   << dendl;
-    throw rgw::io::Exception(EINVAL, std::system_category());
-  }
+  bool unsigned_chunked = flags & AWSv4ComplMulti::FLAG_UNSIGNED_CHUNKED;
+  bool expect_chunk_signature = !unsigned_chunked; // for now
+
+  ldout(cct, 20) << "AWSv4ComplMulti::create_next() old.cnt: " << old.cnt << dendl;
 
   char* data_field_end;
   /* strtoull ignores the "\r\n" sequence after each non-first chunk. */
@@ -1059,45 +1067,81 @@ AWSv4ComplMulti::ChunkMeta::create_next(CephContext* const cct,
     throw rgw::io::Exception(EINVAL, std::system_category());
   }
 
-  /* Parse the chunk_signature=... part. */
-  const auto signature_part = metastr.substr(semicolon_pos + 1);
-  const size_t eq_sign_pos = signature_part.find("=");
-  if (eq_sign_pos == std::string_view::npos) {
-    ldout(cct, 20) << "AWSv4ComplMulti: cannot find the '=' separator"
-                   << dendl;
-    throw rgw::io::Exception(EINVAL, std::system_category());
-  }
+  if (expect_chunk_signature) {
 
-  /* OK, we have at least the beginning of a signature. */
-  const size_t data_sep_pos = signature_part.find("\r\n");
-  if (data_sep_pos == std::string_view::npos) {
-    ldout(cct, 20) << "AWSv4ComplMulti: no new line at signature end"
-                   << dendl;
-    throw rgw::io::Exception(EINVAL, std::system_category());
-  }
+    /* traditional parse looks for
+       string(IntHexBase(chunk-size)) + ";chunk-signature=" + signature + \r\n + chunk-data + \r\n
+       cf. https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html */
 
-  const auto signature = \
-    signature_part.substr(eq_sign_pos + 1, data_sep_pos - 1 - eq_sign_pos);
-  if (signature.length() != SIG_SIZE) {
-    ldout(cct, 20) << "AWSv4ComplMulti: signature.length() != 64"
-                   << dendl;
-    throw rgw::io::Exception(EINVAL, std::system_category());
-  }
+    const size_t semicolon_pos = metastr.find(";");
+    if (semicolon_pos == std::string_view::npos) {
+      ldout(cct, 20) << "AWSv4ComplMulti cannot find the ';' separator"
+                    << dendl;
+      throw rgw::io::Exception(EINVAL, std::system_category());
+    }
 
-  const size_t data_starts_in_stream = \
-    + semicolon_pos + strlen(";") + data_sep_pos  + strlen("\r\n")
-    + old.data_offset_in_stream + old.data_length;
+    /* Parse the chunk_signature=... part. */
+    const auto signature_part = metastr.substr(semicolon_pos + 1);
+    const size_t eq_sign_pos = signature_part.find("=");
+    if (eq_sign_pos == std::string_view::npos) {
+      ldout(cct, 20) << "AWSv4ComplMulti: cannot find the '=' separator"
+                     << dendl;
+      throw rgw::io::Exception(EINVAL, std::system_category());
+    }
 
-  ldout(cct, 20) << "parsed new chunk; signature=" << signature
-                 << ", data_length=" << data_length
-                 << ", data_starts_in_stream=" << data_starts_in_stream
-                 << dendl;
+    /* OK, we have at least the beginning of a signature. */
+    const size_t data_sep_pos = signature_part.find("\r\n");
+    if (data_sep_pos == std::string_view::npos) {
+      ldout(cct, 20) << "AWSv4ComplMulti: no new line at signature end"
+                     << dendl;
+      throw rgw::io::Exception(EINVAL, std::system_category());
+    }
 
-  return std::make_pair(ChunkMeta(data_starts_in_stream,
-                                  data_length,
-                                  signature),
-                        semicolon_pos + 83);
-}
+    const auto signature =
+        signature_part.substr(eq_sign_pos + 1, data_sep_pos - 1 - eq_sign_pos);
+    if (signature.length() != SIG_SIZE) {
+      ldout(cct, 20) << "AWSv4ComplMulti: signature.length() != 64" << dendl;
+      throw rgw::io::Exception(EINVAL, std::system_category());
+    }
+
+    const size_t data_starts_in_stream =
+        +semicolon_pos + sarrlen(";") + data_sep_pos + sarrlen("\r\n") +
+        old.data_offset_in_stream + old.data_length;
+
+    ldout(cct, 20) << "parsed new chunk; signature=" << signature
+                   << ", data_length=" << data_length
+                   << ", data_starts_in_stream=" << data_starts_in_stream
+                   << dendl;
+
+    return std::make_pair(
+            ChunkMeta(data_starts_in_stream, data_length, signature, flags,
+                      ++old.cnt),
+            semicolon_pos + 83);
+  } else {
+    /* no-chunk-signature aws-chunked */
+    ldout(cct, 20) << "AWSv4ComplMulti: non-signature meta chunk; data_length "
+                  << data_length
+                  << dendl;
+
+    /* currently we might see "\r\n20000\r\nreate the directory..." */
+    size_t crlf_pos = metastr.find("\r\n");
+    if (crlf_pos == 0) [[likely]] {
+      crlf_pos = metastr.find("\r\n", 2); // skip to next one
+    }
+    if (crlf_pos == std::string_view::npos) {
+      ldout(cct, 20) << "AWSv4ComplMulti: no new line at expected chunk end"
+                     << dendl;
+      throw rgw::io::Exception(EINVAL, std::system_category());
+    }
+
+    const size_t consumed = crlf_pos + sarrlen("\r\n");
+    const size_t data_starts_in_stream =
+        consumed + old.data_offset_in_stream + old.data_length;
+    return std::make_pair(ChunkMeta(data_starts_in_stream, data_length,
+                           "" /* signature */, flags, ++old.cnt),
+                          consumed);
+  } /* no-signature */
+} /* AWSv4ComplMulti::ChunkMeta::create_next */
 
 std::string
 AWSv4ComplMulti::calc_chunk_signature(const std::string& payload_hash) const
@@ -1110,7 +1154,7 @@ AWSv4ComplMulti::calc_chunk_signature(const std::string& payload_hash) const
     AWS4_EMPTY_PAYLOAD_HASH,
     payload_hash);
 
-  ldout(cct, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
+  ldout(cct(), 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign
                  << dendl;
 
   /* new chunk signature */
@@ -1119,22 +1163,36 @@ AWSv4ComplMulti::calc_chunk_signature(const std::string& payload_hash) const
   return sig.to_str();
 }
 
-
 bool AWSv4ComplMulti::is_signature_mismatched()
 {
+  /* in streaming unsigned payload, there are no chunk signatures nor trailer
+   * signature; there may be a trailing checksum
+   *
+   * if (flags & FLAG_UNSIGNED_CHUNKED), then we assert chunk_meta.signature.empty(),
+   * and conversely
+   */
+  if (flags & FLAG_UNSIGNED_CHUNKED) {
+    return false;
+  }
+
   /* The validity of previous chunk can be verified only after getting meta-
    * data of the next one. */
   const auto payload_hash = calc_hash_sha256_restart_stream(&sha256_hash);
   const auto calc_signature = calc_chunk_signature(payload_hash);
 
+  if (cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 16)) [[unlikely]] {
+    ldout(cct(), 16) << "AWSv4ComplMulti: declared signature="
+                    << chunk_meta.get_signature()
+                    << "\nAWSv4ComplMulti: calculated signature="
+                    << calc_signature << dendl;
+    ldout(cct(), 16) << "AWSv4ComplMulti: prev_chunk_signature="
+                    << prev_chunk_signature << dendl;
+
+  }
+
   if (chunk_meta.get_signature() != calc_signature) {
-    ldout(cct, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
+    ldout(cct(), 16) << "AWSv4ComplMulti: ERROR: chunk signature mismatch"
                    << dendl;
-    ldout(cct, 20) << "AWSv4ComplMulti: declared signature="
-                   << chunk_meta.get_signature() << dendl;
-    ldout(cct, 20) << "AWSv4ComplMulti: calculated signature="
-                   << calc_signature << dendl;
-
     return true;
   } else {
     prev_chunk_signature = chunk_meta.get_signature();
@@ -1142,16 +1200,19 @@ bool AWSv4ComplMulti::is_signature_mismatched()
   }
 }
 
-size_t AWSv4ComplMulti::recv_chunk(char* const buf, const size_t buf_max, bool& eof)
+AWSv4ComplMulti::ReceiveChunkResult AWSv4ComplMulti::recv_chunk(
+  char* const buf, const size_t buf_max, uint32_t cnt, bool& eof)
 {
   /* Buffer stores only parsed stream. Raw values reflect the stream
    * we're getting from a client. */
   size_t buf_pos = 0;
 
+  ldout(cct(), 20) << "AWSv4ComplMulti::recv_chunk() cnt: " << cnt << dendl;
+
   if (chunk_meta.is_new_chunk_in_stream(stream_pos)) {
     /* Verify signature of the previous chunk. We aren't doing that for new
      * one as the procedure requires calculation of payload hash. This code
-     * won't be triggered for the last, zero-length chunk. Instead, is will
+     * won't be triggered for the last, zero-length chunk. Instead, it will
      * be checked in the complete() method.  */
     if (stream_pos >= ChunkMeta::META_MAX_SIZE && is_signature_mismatched()) {
       throw rgw::io::Exception(ERR_SIGNATURE_NO_MATCH, std::system_category());
@@ -1163,8 +1224,24 @@ size_t AWSv4ComplMulti::recv_chunk(char* const buf, const size_t buf_max, bool&
     do {
       const size_t orig_size = parsing_buf.size();
       parsing_buf.resize(parsing_buf.size() + to_extract);
+
+      auto pb_size = parsing_buf.size();
+      auto pb_capacity = parsing_buf.capacity();
+
       const size_t received = io_base_t::recv_body(parsing_buf.data() + orig_size,
                                                    to_extract);
+
+      ldout(cct(), 20) << "AWSv4ComplMulti::recv_chunk() "
+                    << "after io_base_t::recv_body recv pb_size: "
+                    << pb_size
+                    << " pb_capacity "
+                    << pb_capacity
+                    << " to_extract: "
+                    << to_extract
+                    << " received: "
+                    << received
+                    << dendl;
+
       parsing_buf.resize(parsing_buf.size() - (to_extract - received));
       if (received == 0) {
         eof = true;
@@ -1177,14 +1254,14 @@ size_t AWSv4ComplMulti::recv_chunk(char* const buf, const size_t buf_max, bool&
 
     size_t consumed;
     std::tie(chunk_meta, consumed) = \
-      ChunkMeta::create_next(cct, std::move(chunk_meta),
-                             parsing_buf.data(), parsing_buf.size());
+      ChunkMeta::create_next(cct(), std::move(chunk_meta),
+        parsing_buf.data(), parsing_buf.size(), flags);
 
     /* We can drop the bytes consumed during metadata parsing. The remainder
      * can be chunk's data plus possibly beginning of next chunks' metadata. */
     parsing_buf.erase(std::begin(parsing_buf),
                       std::begin(parsing_buf) + consumed);
-  }
+  } /* if (chunk_meta.is_new_chunk_in_stream(stream_pos)) */
 
   size_t stream_pos_was = stream_pos - parsing_buf.size();
 
@@ -1197,9 +1274,16 @@ size_t AWSv4ComplMulti::recv_chunk(char* const buf, const size_t buf_max, bool&
    * the final buffer. This is a trade-off between frontend's read overhead
    * and memcpy. */
   if (to_extract > 0 && parsing_buf.size() > 0) {
+
     const auto data_len = std::min(to_extract, parsing_buf.size());
     const auto data_end_iter = std::begin(parsing_buf) + data_len;
-    dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract << ", data_len=" << data_len << dendl;
+
+    dout(30) << "AWSv4ComplMulti: to_extract=" << to_extract
+            << ", data_len=" << data_len
+            << dendl;
+
+    /* if is-last-frag, then */
+    lf_bytes = stream_pos - stream_pos_was - data_len;
 
     std::copy(std::begin(parsing_buf), data_end_iter, buf);
     parsing_buf.erase(std::begin(parsing_buf), data_end_iter);
@@ -1229,18 +1313,146 @@ size_t AWSv4ComplMulti::recv_chunk(char* const buf, const size_t buf_max, bool&
   }
 
   dout(20) << "AWSv4ComplMulti: filled=" << buf_pos << dendl;
-  return buf_pos;
-}
+  return ReceiveChunkResult(buf_pos, chunk_meta.get_offset());
+} /* AWSv4Complmulti::recv_chunk */
+
+std::string
+AWSv4ComplMulti::calc_v4_trailer_signature(const trailer_map_t& trailer_map,
+                                          const std::string_view last_chunk_sig)
+{
+  const auto headers = get_canon_amz_hdrs(trailer_map);
+  const auto canon_header_hash = calc_hash_sha256(headers);
+
+  const auto string_to_sign = string_join_reserve("\n",
+    "AWS4-HMAC-SHA256-TRAILER",
+    date,
+    credential_scope,
+    last_chunk_sig,
+    canon_header_hash.to_str());
+
+  const auto trailer_signature =
+    calc_hmac_sha256(signing_key, string_to_sign).to_str();
+
+  ldout(cct(), 10) << "trailer headers = " << headers
+                << "\ntrailers string to sign = "
+                 << rgw::crypt_sanitize::log_content{string_to_sign}
+                << "\ncalc trailer signature = "
+                << trailer_signature
+                << "\nexpected last-chunk-sig = "
+                << last_chunk_sig
+                << dendl;
+
+  return trailer_signature;
+} /* calc_v4_trailer_signature */
+
+
+/* the following templates capture the start (and for extract_helper)
+ * end boundaries of a substring match as constant strings, moving
+ * a small amount of work to compile time */
+
+using ExtractResult = std::tuple<bool, std::string_view, size_t>;
+
+/* adapted from here: https://ctrpeach.io/posts/cpp20-string-literal-template-parameters/ */
+template<size_t N>
+struct StringLiteral {
+    constexpr StringLiteral(const char (&str)[N]) {
+        std::copy_n(str, N, val);
+    }
+    char val[N];
+};
+
+template <StringLiteral start, StringLiteral end>
+static inline ExtractResult mut_extract_helper(std::string_view& region) {
+  if (auto spos = region.find(start.val);
+      spos != std::string_view::npos) {
+    if (auto epos = region.find(end.val, spos + sarrlen(start.val));
+       epos != std::string_view::npos) {
+      std::string_view matched = region.substr(spos, epos - spos);
+      auto consumed = matched.size() + sarrlen(end.val);
+      region.remove_prefix(consumed);
+      return ExtractResult(true, matched, spos + consumed);
+    }
+  }
+  return ExtractResult(false, "", 0);
+} /* mut_extract_helper <begin, end> */
+
+template <StringLiteral end>
+static inline ExtractResult extract_helper(const std::string_view& region,
+                                          const std::string_view start) {
+  if (auto spos = region.find(start);
+      spos != std::string_view::npos) {
+    if (auto epos = region.find(end.val, spos + start.length());
+       epos != std::string_view::npos) {
+      std::string_view matched = region.substr(spos, epos - spos);
+      auto consumed = matched.size() + sarrlen(end.val);
+      return ExtractResult(true, matched, consumed);
+    }
+  }
+  return ExtractResult(false, "", 0);
+} /* extract_helper <end> */
+
+using split_func_t =
+  const fu2::unique_function<void(const std::string_view k,
+                                 const std::string_view v) const>;
+
+static inline void split_header(const std::string_view hdr, split_func_t f)
+{
+  auto kv = ceph::split(hdr, ":");
+  auto k = kv.begin();
+  if (k != kv.end()) {
+    auto v = std::next(k);
+    if (v != kv.end()) [[likely]] {
+      f(*k, *v);
+    }
+  }
+} /* split_header */
+
+inline void AWSv4ComplMulti::extract_trailing_headers(
+  std::string_view x_amz_trailer, std::string_view& mut_sv_trailer,
+  AWSv4ComplMulti::trailer_map_t& trailer_map)
+{
+  using std::get;
+  size_t consumed = 0;
+  /* spliterate x_amz_trailer */
+  auto kv = ceph::split(x_amz_trailer, ",");
+  for (auto k = kv.begin(); k != kv.end(); k = std::next(k)) {
+    /* extract trailer;  if there's more than one trailer, don't rely on their
+     * order in x-amz-trailer */
+    auto ex_header = extract_helper<"\r\n">(mut_sv_trailer, *k);
+    if (get<0>(ex_header)) {
+      auto header = get<1>(ex_header);
+      split_header(header, [&](const std::string_view k, const std::string_view v) -> void {
+       if (cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 10)) [[unlikely]] {
+         ldout(cct(), 10) << fmt::format("\nextracted trailing header {}={}", k, v) << dendl;
+       }
+       /* populate trailer map with expected headers and their values, if sent */
+       trailer_map.insert(trailer_map_t::value_type(k, v));
+       /* populate to req_info.env as well */
+       put_prop(ys_header_mangle(k), v);
+      });
+      consumed += get<2>(ex_header);
+    } /* one trailer */
+  } /* foreach trailer */
+  /* advance mut_sv_trailer */
+  mut_sv_trailer.remove_prefix(consumed);
+} /* AWSv4complmulti::extract_trailing_headers */
 
 size_t AWSv4ComplMulti::recv_body(char* const buf, const size_t buf_max)
 {
+  using std::get;
+
   bool eof = false;
   size_t total = 0;
 
+  ldout(cct(), 20) << "AWSv4ComplMulti::recv_body() buf_max: " << buf_max << dendl;
+
+  uint32_t cnt = 0;
   while (total < buf_max && !eof) {
-    const size_t received = recv_chunk(buf + total, buf_max - total, eof);
-    total += received;
+    ReceiveChunkResult rcr =
+      recv_chunk(buf + total, buf_max - total, cnt++, eof);
+    total += rcr.received;
   }
+
   dout(20) << "AWSv4ComplMulti: received=" << total << dendl;
   return total;
 }
@@ -1253,6 +1465,7 @@ void AWSv4ComplMulti::modify_request_state(const DoutPrefixProvider* dpp, req_st
   if (!decoded_length) {
     throw -EINVAL;
   } else {
+    /* XXXX oh my, we forget the original content length */
     s_rw->length = decoded_length;
     s_rw->content_length = parse_content_length(decoded_length);
 
@@ -1269,21 +1482,135 @@ void AWSv4ComplMulti::modify_request_state(const DoutPrefixProvider* dpp, req_st
 
 bool AWSv4ComplMulti::complete()
 {
-  /* Now it's time to verify the signature of the last, zero-length chunk. */
+  /* historically, this code has been validating not the final zero-length
+   * chunk, but the one before that; we'll do that as before, and then
+   * consume the last chunk signature and the trailer section */
   if (is_signature_mismatched()) {
-    ldout(cct, 10) << "ERROR: signature of last chunk does not match"
+    ldout(cct(), 10) << "ERROR: signature of last payload chunk does not match"
                    << dendl;
     return false;
   } else {
+    /* now it's time to verify the signature of the last, zero-length chunk */
+    const auto string_to_sign = string_join_reserve("\n",
+    "AWS4-HMAC-SHA256-PAYLOAD",
+    date,
+    credential_scope,
+    prev_chunk_signature,
+    AWS4_EMPTY_PAYLOAD_HASH,
+    AWS4_EMPTY_PAYLOAD_HASH);
+
+    const auto final_chunk_signature =
+      calc_hmac_sha256(signing_key, string_to_sign).to_str();
+
+    ldout(cct(), 10) << "final chunk signature = "
+                    << final_chunk_signature
+                    << "\nprev_chunk_signature was "
+                    << prev_chunk_signature
+                    << dendl;
+
+    /* in the last-chunk case, parsing_buf potentially holds unconsumed
+     * data, including the final chunk boundary */
+    std::string_view last_frag(parsing_buf.begin().get_ptr(), lf_bytes);
+
+    size_t tbuf_pos = 0;
+
+    static constexpr size_t trailer_buf_size = 256;
+    boost::container::static_vector<char, trailer_buf_size> trailer_vec;
+
+    std::copy(parsing_buf.begin(), parsing_buf.begin() + lf_bytes,
+              trailer_vec.begin());
+    tbuf_pos += lf_bytes;
+
+    while (tbuf_pos < trailer_buf_size) {
+      const size_t received =
+          io_base_t::recv_body(trailer_vec.data() + tbuf_pos,
+                              trailer_buf_size - tbuf_pos - 1);
+      dout(30) << "AWSv4ComplMulti: recv trailer received=" << received
+               << dendl;
+      if (received == 0) {
+        break;
+      }
+      tbuf_pos += received;
+    }
+
+    if (tbuf_pos == trailer_buf_size) {
+      ldout(cct(), 10) << "AWSv4ComplMulti:: recv trailer exceeded size limit of "
+                      << trailer_buf_size - 1
+                      << " bytes"
+                      << dendl;
+      throw rgw::io::Exception(ERR_LIMIT_EXCEEDED, std::system_category());
+    }
+
+    std::string_view expected_trailer_signature;
+    std::string calculated_trailer_signature;
+
+    if (tbuf_pos > sarrlen("\r\n0;")) {
+      const std::string_view sv_trailer(trailer_vec.data() + sarrlen("\r\n0;"),
+                                        tbuf_pos - sarrlen("\r\n0;"));
+
+      if (cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 10)) [[unlikely]] {
+        ldout(cct(), 10) << "trailer_section: " << sv_trailer << dendl;
+      }
+
+      std::string_view mut_sv_trailer(sv_trailer);
+
+      auto chunk_signature =
+          mut_extract_helper<"chunk-signature=", "\r\n">(mut_sv_trailer);
+
+      std::string_view sig_sv;
+      if (get<0>(chunk_signature)) {
+        sig_sv = get<1>(chunk_signature);
+        sig_sv.remove_prefix(sarrlen("chunk-signature="));
+
+        ldout(cct(), 10) << "expected last chunk signature: " << sig_sv
+                         << " remaining: " << mut_sv_trailer << dendl;
+      }
+
+      trailer_map_t trailer_map;
+
+      if (x_amz_trailer) {
+        extract_trailing_headers(*x_amz_trailer, mut_sv_trailer, trailer_map);
+      }
+
+      auto trailer_signature =
+          mut_extract_helper<"x-amz-trailer-signature:", "\r\n">(
+              mut_sv_trailer);
+      if (get<0>(trailer_signature)) {
+        auto trailing_sig = get<1>(trailer_signature);
+        split_header(
+            trailing_sig,
+            [&](const std::string_view k, const std::string_view v) -> void {
+              expected_trailer_signature = v;
+            });
+
+        calculated_trailer_signature =
+            calc_v4_trailer_signature(trailer_map, final_chunk_signature);
+
+        ldout(cct(), 10) << "expected trailer signature="
+                         << expected_trailer_signature
+                         << "\n calculated trailer signature="
+                         << calculated_trailer_signature
+                         << "\n trailer bytes remaining/not consumed: "
+                         << mut_sv_trailer << dendl;
+      } /* matched trailer signature */
+    } /* have trailer */
+
+    if (expect_trailer_signature() &&
+       (expected_trailer_signature.empty() ||
+        (calculated_trailer_signature != expected_trailer_signature))) {
+      throw rgw::io::Exception(ERR_SIGNATURE_NO_MATCH, std::system_category());
+    }
+
     return true;
   }
-}
+} /* AWSv4Complmulti:: complete */
 
 rgw::auth::Completer::cmplptr_t
 AWSv4ComplMulti::create(const req_state* const s,
                         std::string_view date,
                         std::string_view credential_scope,
                         std::string_view seed_signature,
+                       uint32_t flags,
                         const boost::optional<std::string>& secret_key)
 {
   if (!secret_key) {
@@ -1300,6 +1627,7 @@ AWSv4ComplMulti::create(const req_state* const s,
                                            std::move(date),
                                            std::move(credential_scope),
                                            std::move(seed_signature),
+                                          flags,
                                            signing_key);
 }
 
index c03dfad825dd2abddd0b655f9238589fa8188714..f6876820056538cbe6e8645061190ac5ddcc1018 100644 (file)
@@ -4,6 +4,8 @@
 #pragma once
 
 #include <array>
+#include <boost/algorithm/string/predicate.hpp>
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <string_view>
@@ -11,6 +13,7 @@
 
 #include <boost/algorithm/string.hpp>
 #include <boost/container/static_vector.hpp>
+#include <boost/container/flat_map.hpp>
 
 #include "common/sstring.hh"
 #include "rgw_common.h"
@@ -256,37 +259,47 @@ public:
   const char* get_name() const noexcept override {
     return "rgw::auth::s3::AWSAuthStrategy";
   }
-};
-
+}; /* AWSAuthstrategy */
 
 class AWSv4ComplMulti : public rgw::auth::Completer,
                         public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>,
                         public std::enable_shared_from_this<AWSv4ComplMulti> {
+
   using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>;
   using signing_key_t = sha256_digest_t;
 
-  CephContext* const cct;
+  using trailer_map_t = boost::container::flat_map<std::string_view, std::string_view>;
+
+  const req_state* const s;
 
   const std::string_view date;
   const std::string_view credential_scope;
+  const uint32_t flags;
   const signing_key_t signing_key;
 
   class ChunkMeta {
     size_t data_offset_in_stream = 0;
     size_t data_length = 0;
     std::string signature;
+    uint32_t flags{FLAG_NONE};
+    uint32_t cnt;
 
     ChunkMeta(const size_t data_starts_in_stream,
               const size_t data_length,
-              const std::string_view signature)
+              const std::string_view signature,
+             uint32_t _flags,
+             uint32_t _cnt)
       : data_offset_in_stream(data_starts_in_stream),
         data_length(data_length),
-        signature(std::string(signature)) {
-    }
+        signature(std::string(signature)),
+       flags(_flags),
+       cnt(_cnt)
+    {}
 
-    explicit ChunkMeta(const std::string_view& signature)
-      : signature(std::string(signature)) {
-    }
+    explicit ChunkMeta(const std::string_view& signature, uint32_t _flags,
+                      uint32_t _cnt)
+      : signature(std::string(signature)), flags(_flags), cnt(_cnt)
+    {}
 
   public:
     static constexpr size_t SIG_SIZE = 64;
@@ -309,10 +322,14 @@ class AWSv4ComplMulti : public rgw::auth::Completer,
       return signature;
     }
 
+    size_t get_offset() { return data_offset_in_stream; }
+
     /* Factory: create an object representing metadata of first, initial chunk
      * in a stream. */
-    static ChunkMeta create_first(const std::string_view& seed_signature) {
-      return ChunkMeta(seed_signature);
+    static ChunkMeta create_first(const std::string_view& seed_signature,
+                                 uint32_t flags,
+                                 uint32_t cnt) {
+      return ChunkMeta(seed_signature, flags, cnt);
     }
 
     /* Factory: parse a block of META_MAX_SIZE bytes and creates an object
@@ -321,38 +338,87 @@ class AWSv4ComplMulti : public rgw::auth::Completer,
     static std::pair<ChunkMeta, size_t> create_next(CephContext* cct,
                                                     ChunkMeta&& prev,
                                                     const char* metabuf,
-                                                    size_t metabuf_len);
+                                                    size_t metabuf_len,
+                                                   uint32_t flags);
   } chunk_meta;
 
+  uint16_t lf_bytes;
   size_t stream_pos;
   boost::container::static_vector<char, ChunkMeta::META_MAX_SIZE> parsing_buf;
+  boost::optional<std::string_view> x_amz_trailer;
   ceph::crypto::SHA256* sha256_hash;
   std::string prev_chunk_signature;
 
   bool is_signature_mismatched();
   std::string calc_chunk_signature(const std::string& payload_hash) const;
-  size_t recv_chunk(char* buf, size_t max, bool& eof);
 
-public:
+  struct ReceiveChunkResult {
+    size_t received;
+    size_t data_offset_in_stream;
+
+    ReceiveChunkResult(size_t x, size_t y)
+      : received(x), data_offset_in_stream(y)
+    {}
+  }; /* ReceiveChunkResult */
+
+  inline CephContext* cct() const {
+    return s->cct;
+  }
+
+  inline bool expect_trailer_signature() const {
+    return flags & AWSv4ComplMulti::FLAG_TRAILER_SIGNATURE;
+  }
+
+  inline void put_prop(const std::string_view k, const std::string_view v) {
+    /* assume the caller will mangle the key name, if required */
+    auto& map = const_cast<env_map_t&>(s->info.env->get_map());
+    map.insert(env_map_t::value_type(k, v));
+  }
+
+  inline void extract_trailing_headers(std::string_view x_amz_trailer,
+                                      std::string_view& mut_sv_trailer,
+                                      trailer_map_t& trailer_map);
+
+  std::string calc_v4_trailer_signature(const trailer_map_t& trailer_map,
+                                       const std::string_view last_chunk_sig);
+
+  ReceiveChunkResult recv_chunk(char* buf, size_t max, uint32_t rc_cnt, bool& eof);
+
+  public:
+
+  static constexpr uint32_t FLAG_NONE =              0x00;
+  static constexpr uint32_t FLAG_TRAILING_CHECKSUM = 0x01;
+  static constexpr uint32_t FLAG_UNSIGNED_PAYLOAD =  0x02;
+  static constexpr uint32_t FLAG_UNSIGNED_CHUNKED =  0x04;
+  static constexpr uint32_t FLAG_TRAILER_SIGNATURE = 0x08;
+
   /* We need the constructor to be public because of the std::make_shared that
    * is employed by the create() method. */
   AWSv4ComplMulti(const req_state* const s,
                   std::string_view date,
                   std::string_view credential_scope,
                   std::string_view seed_signature,
+                 uint32_t _flags,
                   const signing_key_t& signing_key)
     : io_base_t(nullptr),
-      cct(s->cct),
+      s(s),
       date(std::move(date)),
       credential_scope(std::move(credential_scope)),
+      flags(_flags),
       signing_key(signing_key),
 
       /* The evolving state. */
-      chunk_meta(ChunkMeta::create_first(seed_signature)),
+      chunk_meta(ChunkMeta::create_first(
+                  seed_signature, flags, 0 /* first call in cycle */)),
       stream_pos(0),
       sha256_hash(calc_hash_sha256_open_stream()),
-      prev_chunk_signature(std::move(seed_signature)) {
-  }
+      prev_chunk_signature(std::move(seed_signature))
+  {
+    auto cksum = s->info.env->get("HTTP_X_AMZ_TRAILER");
+    if (!! cksum) {
+      x_amz_trailer = std::string_view(cksum, std::strlen(cksum));
+    }
+  } /* AWSv4ComplMulti */
 
   ~AWSv4ComplMulti() {
     if (sha256_hash) {
@@ -372,6 +438,7 @@ public:
                           std::string_view date,
                           std::string_view credential_scope,
                           std::string_view seed_signature,
+                         uint32_t flags,
                           const boost::optional<std::string>& secret_key);
 
 };
@@ -454,6 +521,13 @@ static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD";
 static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \
   "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
 
+/* trailing header forms */
+static constexpr char AWS4_STREAMING_UNSIGNED_PAYLOAD_TRAILER[] = \
+  "STREAMING-UNSIGNED-PAYLOAD-TRAILER";
+
+static constexpr char AWS4_STREAMING_HMAC_SHA256_PAYLOAD_TRAILER[] = \
+  "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER";
+
 bool is_non_s3_op(RGWOpType op_type);
 
 int parse_v4_credentials(const req_info& info,                     /* in */
@@ -578,11 +652,31 @@ static inline const char* get_v4_exp_payload_hash(const req_info& info)
   return expected_request_payload_hash;
 }
 
-static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash)
+static inline bool is_traditional_v4_unsigned_payload(const char* const exp_payload_hash)
 {
   return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH);
 }
 
+static inline bool is_v4_payload_unsigned_chunked(const char* const exp_payload_hash)
+{
+  return boost::equals(exp_payload_hash, AWS4_STREAMING_UNSIGNED_PAYLOAD_TRAILER);
+}
+
+static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash)
+{
+  return boost::contains(exp_payload_hash, "UNSIGNED-PAYLOAD");
+}
+
+static inline bool have_checksum_trailer(const char* const exp_payload_hash)
+{
+  return boost::algorithm::ends_with(exp_payload_hash, "TRAILER");
+}
+
+static inline bool expect_trailer_signature(const char* const exp_payload_hash)
+{
+  return boost::equals(exp_payload_hash, AWS4_STREAMING_HMAC_SHA256_PAYLOAD_TRAILER);
+}
+
 static inline bool is_v4_payload_empty(const req_state* const s)
 {
   /* from rfc2616 - 4.3 Message Body
@@ -596,7 +690,7 @@ static inline bool is_v4_payload_empty(const req_state* const s)
 
 static inline bool is_v4_payload_streamed(const char* const exp_payload_hash)
 {
-  return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH);
+  return boost::algorithm::starts_with(exp_payload_hash, "STREAMING-");
 }
 
 std::string get_v4_canonical_qs(const req_info& info, bool using_qs);
index 3bf6c4dfac8e9715d1bb26ba130e0f1ca75152a1..ea91859f5e6823bd7223bb74a23a1c4b7cdfc00f 100644 (file)
@@ -446,8 +446,10 @@ public:
   }
 };
 
+using env_map_t = std::map<std::string, std::string, ltstr_nocase>;
+
 class RGWEnv {
-  std::map<std::string, std::string, ltstr_nocase> env_map;
+  env_map_t env_map;
   RGWConf conf;
 public:
   void init(CephContext *cct);
@@ -1849,6 +1851,18 @@ int decode_bl(bufferlist& bl, T& t)
   return 0;
 }
 
+static inline std::string ys_header_mangle(std::string_view name)
+{
+  /* can we please stop doing this? */
+  std::string out;
+  out.reserve(name.length());
+  std::transform(std::begin(name), std::end(name),
+                std::back_inserter(out), [](const int c) {
+                  return c == '-' ? '_' : c == '_' ? '-' : std::toupper(c);
+                });
+  return out;
+} /* ys_header_mangle */
+
 extern int rgw_bucket_parse_bucket_instance(const std::string& bucket_instance, std::string *bucket_name, std::string *bucket_id, int *shard_id);
 
 boost::intrusive_ptr<CephContext>
index 5658003adb8a370b6169617489b96d7896c123be..d50846a255cbb915fb06c6ddac35c5e37d98ed1c 100644 (file)
@@ -260,7 +260,7 @@ namespace rgw {
     static constexpr uint32_t FLAG_ROOT =    0x0002;
     static constexpr uint32_t FLAG_CREATE =  0x0004;
     static constexpr uint32_t FLAG_CREATING =  0x0008;
-    static constexpr uint32_t FLAG_SYMBOLIC_LINK = 0x0009;
+    static constexpr uint32_t FLAG_SYMBOLIC_LINK = 0x0009; // XXXX bug?
     static constexpr uint32_t FLAG_DIRECTORY = 0x0010;
     static constexpr uint32_t FLAG_BUCKET = 0x0020;
     static constexpr uint32_t FLAG_LOCK =   0x0040;
index 6b827f983fb625a2d463dd75ddd9d799310e6e32..a7c35f3773ccc1b0152d02831e14ad1f346c11e6 100644 (file)
@@ -1080,7 +1080,7 @@ int RGWPutObj_ObjStore::get_data(bufferlist& bl)
   }
 
   return len;
-}
+} /* RGWPutObj_ObjStore::get_data(bufferlist& bl) */
 
 
 /*
@@ -2010,23 +2010,6 @@ RGWRESTMgr::~RGWRESTMgr()
   delete default_mgr;
 }
 
-int64_t parse_content_length(const char *content_length)
-{
-  int64_t len = -1;
-
-  if (*content_length == '\0') {
-    len = 0;
-  } else {
-    string err;
-    len = strict_strtoll(content_length, 10, &err);
-    if (!err.empty()) {
-      len = -1;
-    }
-  }
-
-  return len;
-}
-
 int RGWREST::preprocess(req_state *s, rgw::io::BasicClient* cio)
 {
   req_info& info = s->info;
index 0b6cf62ed2c732ddcacf5eb5850de8376c0d96c9..8ee587e7c7b473296cec8b6cdf8459eb47873db9 100644 (file)
@@ -8,6 +8,7 @@
 #include <string_view>
 #include <boost/container/flat_set.hpp>
 #include "common/sstring.hh"
+#include "common/strtol.h"
 #include "common/ceph_json.h"
 #include "include/ceph_assert.h" /* needed because of common/ceph_json.h */
 #include "rgw_op.h"
@@ -782,6 +783,23 @@ inline void dump_header_if_nonempty(req_state* s,
   }
 }
 
+static inline int64_t parse_content_length(const char *content_length)
+{
+  int64_t len = -1;
+
+  if (*content_length == '\0') {
+    len = 0;
+  } else {
+    std::string err;
+    len = strict_strtoll(content_length, 10, &err);
+    if (!err.empty()) {
+      len = -1;
+    }
+  }
+
+  return len;
+} /* parse_content_length */
+
 inline std::string compute_domain_uri(const req_state *s) {
   std::string uri = (!s->info.domain.empty()) ? s->info.domain :
     [&s]() -> std::string {
@@ -799,7 +817,6 @@ inline std::string compute_domain_uri(const req_state *s) {
 }
 
 extern void dump_content_length(req_state *s, uint64_t len);
-extern int64_t parse_content_length(const char *content_length);
 extern void dump_etag(req_state *s,
                       const std::string_view& etag,
                       bool quoted = false);
index 63bdaeff585975590ac9bbc8a23f58c30dbffea5..fddb3f092c1a899384fd53f6e3e71072592c77fe 100644 (file)
@@ -1,6 +1,7 @@
 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab ft=cpp
 
+#include <cstdint>
 #include <errno.h>
 #include <array>
 #include <string.h>
@@ -5628,9 +5629,9 @@ AWSSignerV4::prepare(const DoutPrefixProvider *dpp,
    *   because the URL is used to upload an arbitrary payload. Instead, you
    *   use a constant string UNSIGNED-PAYLOAD.
    *
-   * This means we have absolutely no business in spawning completer. Both
-   * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
-   * by default. We don't need to change that. */
+   * This means that, in the absence of a trailer, we don't need to spawn
+   * a completer. Both aws4_auth_needs_complete and aws4_auth_streaming_mode
+   * are set to false by default. We don't need to change that. */
   return {
     access_key_id,
     date,
@@ -5768,19 +5769,22 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s,
     throw -ERR_AMZ_CONTENT_SHA256_MISMATCH;
   }
 
-  /* Requests authenticated with the Query Parameters are treated as unsigned.
-   * From "Authenticating Requests: Using Query Parameters (AWS Signature
-   * Version 4)":
-   *
-   *   You don't include a payload hash in the Canonical Request, because
-   *   when you create a presigned URL, you don't know the payload content
-   *   because the URL is used to upload an arbitrary payload. Instead, you
-   *   use a constant string UNSIGNED-PAYLOAD.
-   *
-   * This means we have absolutely no business in spawning completer. Both
-   * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
-   * by default. We don't need to change that. */
-  if (is_v4_payload_unsigned(exp_payload_hash) || is_v4_payload_empty(s) || is_non_s3_op) {
+  /* Traditional UNSIGNED-PAYLOAD requests do not require a completer, but since
+   * 2022, even unsigned payload requests can be sent as aws-chunked and may
+   * have a checksum trailer */
+
+  auto traditional_v4_unsigned =
+    is_traditional_v4_unsigned_payload(exp_payload_hash);
+  auto v4_unsigned = is_v4_payload_unsigned(exp_payload_hash);
+  auto v4_unsigned_chunked = is_v4_payload_unsigned_chunked(exp_payload_hash);
+  auto checksum_trailer = have_checksum_trailer(exp_payload_hash);
+  auto trailer_signature = expect_trailer_signature(exp_payload_hash);
+
+  if (traditional_v4_unsigned ||
+      (v4_unsigned && !checksum_trailer) ||
+      is_v4_payload_empty(s) ||
+      is_non_s3_op) {
+    ldpp_dout(s, 10) << __func__ << ": UNSIGNED-PAYLOAD or other v4 no-completer case" << dendl;
     return {
       access_key_id,
       client_signature,
@@ -5855,9 +5859,6 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s,
         cmpl_factory
       };
     } else {
-      /* IMHO "streamed" doesn't fit too good here. I would prefer to call
-       * it "chunked" but let's be coherent with Amazon's terminology. */
-
       ldpp_dout(s, 10) << "body content detected in multiple chunks" << dendl;
 
       /* payload in multiple chunks */
@@ -5883,11 +5884,31 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s,
       /* In the case of query string-based authentication there should be no
        * x-amz-content-sha256 header and the value "UNSIGNED-PAYLOAD" is used
        * for CanonReq. */
+
+      uint32_t flags{AWSv4ComplMulti::FLAG_NONE};
+
+      if (v4_unsigned) {
+       flags |= AWSv4ComplMulti::FLAG_UNSIGNED_PAYLOAD;
+      }
+
+      if (checksum_trailer) {
+       flags |= AWSv4ComplMulti::FLAG_TRAILING_CHECKSUM;
+      }
+
+      if (v4_unsigned_chunked) {
+       flags |= AWSv4ComplMulti::FLAG_UNSIGNED_CHUNKED;
+      }
+
+      if (trailer_signature) {
+       flags |= AWSv4ComplMulti::FLAG_TRAILER_SIGNATURE;
+      }
+
       const auto cmpl_factory = std::bind(AWSv4ComplMulti::create,
                                           s,
                                           date,
                                           credential_scope,
                                           client_signature,
+                                         flags,
                                           std::placeholders::_1);
       return {
         access_key_id,