]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/msg/async/crypto_onwire.cc
import 15.2.5
[ceph.git] / ceph / src / msg / async / crypto_onwire.cc
index c39632cbd6e1e1b595fb6e97983dcf47188d4dda..4e42340603e7d671e6ecb8846ba512dc62948bdf 100644 (file)
@@ -20,8 +20,8 @@ static constexpr const std::size_t AESGCM_TAG_LEN{16};
 static constexpr const std::size_t AESGCM_BLOCK_LEN{16};
 
 struct nonce_t {
-  ceph_le32 random_seq;
-  ceph_le64 random_rest;
+  ceph_le32 fixed;
+  ceph_le64 counter;
 
   bool operator==(const nonce_t& rhs) const {
     return !memcmp(this, &rhs, sizeof(*this));
@@ -41,15 +41,18 @@ class AES128GCM_OnWireTxHandler : public ceph::crypto::onwire::TxHandler {
   ceph::bufferlist buffer;
   nonce_t nonce, initial_nonce;
   bool used_initial_nonce;
+  bool new_nonce_format;  // 64-bit counter?
   static_assert(sizeof(nonce) == AESGCM_IV_LEN);
 
 public:
   AES128GCM_OnWireTxHandler(CephContext* const cct,
                            const key_t& key,
-                           const nonce_t& nonce)
+                           const nonce_t& nonce,
+                           bool new_nonce_format)
     : cct(cct),
       ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free),
-      nonce(nonce), initial_nonce(nonce), used_initial_nonce(false) {
+      nonce(nonce), initial_nonce(nonce), used_initial_nonce(false),
+      new_nonce_format(new_nonce_format) {
     ceph_assert_always(ectx);
     ceph_assert_always(key.size() * CHAR_BIT == 128);
 
@@ -69,20 +72,14 @@ public:
     ::ceph::crypto::zeroize_for_security(&initial_nonce, sizeof(initial_nonce));
   }
 
-  std::uint32_t calculate_segment_size(std::uint32_t size) override
-  {
-    return size;
-  }
-
-  void reset_tx_handler(
-    std::initializer_list<std::uint32_t> update_size_sequence) override;
+  void reset_tx_handler(const uint32_t* first, const uint32_t* last) override;
 
   void authenticated_encrypt_update(const ceph::bufferlist& plaintext) override;
   ceph::bufferlist authenticated_encrypt_final() override;
 };
 
-void AES128GCM_OnWireTxHandler::reset_tx_handler(
-  std::initializer_list<std::uint32_t> update_size_sequence)
+void AES128GCM_OnWireTxHandler::reset_tx_handler(const uint32_t* first,
+                                                 const uint32_t* last)
 {
   if (nonce == initial_nonce) {
     if (used_initial_nonce) {
@@ -96,15 +93,23 @@ void AES128GCM_OnWireTxHandler::reset_tx_handler(
     throw std::runtime_error("EVP_EncryptInit_ex failed");
   }
 
-  buffer.reserve(std::accumulate(std::begin(update_size_sequence),
-    std::end(update_size_sequence), AESGCM_TAG_LEN));
+  ceph_assert(buffer.get_append_buffer_unused_tail_length() == 0);
+  buffer.reserve(std::accumulate(first, last, AESGCM_TAG_LEN));
 
-  nonce.random_seq = nonce.random_seq + 1;
+  if (!new_nonce_format) {
+    // msgr2.0: 32-bit counter followed by 64-bit fixed field,
+    // susceptible to overflow!
+    nonce.fixed = nonce.fixed + 1;
+  } else {
+    nonce.counter = nonce.counter + 1;
+  }
 }
 
 void AES128GCM_OnWireTxHandler::authenticated_encrypt_update(
   const ceph::bufferlist& plaintext)
 {
+  ceph_assert(buffer.get_append_buffer_unused_tail_length() >=
+              plaintext.length());
   auto filler = buffer.append_hole(plaintext.length());
 
   for (const auto& plainbuf : plaintext.buffers()) {
@@ -131,6 +136,8 @@ void AES128GCM_OnWireTxHandler::authenticated_encrypt_update(
 ceph::bufferlist AES128GCM_OnWireTxHandler::authenticated_encrypt_final()
 {
   int final_len = 0;
+  ceph_assert(buffer.get_append_buffer_unused_tail_length() ==
+              AESGCM_BLOCK_LEN);
   auto filler = buffer.append_hole(AESGCM_BLOCK_LEN);
   if(1 != EVP_EncryptFinal_ex(ectx.get(),
        reinterpret_cast<unsigned char*>(filler.c_str()),
@@ -158,16 +165,17 @@ class AES128GCM_OnWireRxHandler : public ceph::crypto::onwire::RxHandler {
   CephContext* const cct;
   std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)> ectx;
   nonce_t nonce;
+  bool new_nonce_format;  // 64-bit counter?
   static_assert(sizeof(nonce) == AESGCM_IV_LEN);
 
 public:
   AES128GCM_OnWireRxHandler(CephContext* const cct,
                            const key_t& key,
-                           const nonce_t& nonce)
+                           const nonce_t& nonce,
+                           bool new_nonce_format)
     : cct(cct),
       ectx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free),
-      nonce(nonce)
-  {
+      nonce(nonce), new_nonce_format(new_nonce_format) {
     ceph_assert_always(ectx);
     ceph_assert_always(key.size() * CHAR_BIT == 128);
 
@@ -190,12 +198,8 @@ public:
     return AESGCM_TAG_LEN;
   }
   void reset_rx_handler() override;
-  ceph::bufferlist authenticated_decrypt_update(
-    ceph::bufferlist&& ciphertext,
-    std::uint32_t alignment) override;
-  ceph::bufferlist authenticated_decrypt_update_final(
-    ceph::bufferlist&& ciphertext,
-    std::uint32_t alignment) override;
+  void authenticated_decrypt_update(ceph::bufferlist& bl) override;
+  void authenticated_decrypt_update_final(ceph::bufferlist& bl) override;
 };
 
 void AES128GCM_OnWireRxHandler::reset_rx_handler()
@@ -204,68 +208,46 @@ void AES128GCM_OnWireRxHandler::reset_rx_handler()
        reinterpret_cast<const unsigned char*>(&nonce))) {
     throw std::runtime_error("EVP_DecryptInit_ex failed");
   }
-  nonce.random_seq = nonce.random_seq + 1;
+
+  if (!new_nonce_format) {
+    // msgr2.0: 32-bit counter followed by 64-bit fixed field,
+    // susceptible to overflow!
+    nonce.fixed = nonce.fixed + 1;
+  } else {
+    nonce.counter = nonce.counter + 1;
+  }
 }
 
-ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update(
-  ceph::bufferlist&& ciphertext,
-  std::uint32_t alignment)
+void AES128GCM_OnWireRxHandler::authenticated_decrypt_update(
+  ceph::bufferlist& bl)
 {
-  ceph_assert(ciphertext.length() > 0);
-  //ceph_assert(ciphertext.length() % AESGCM_BLOCK_LEN == 0);
-
-  // NOTE: we might consider in-place transformations in the future. AFAIK
-  // OpenSSL's might sustain that but lack of clear confirmation postpones.
-  auto plainnode = ceph::buffer::ptr_node::create(buffer::create_aligned(
-    ciphertext.length(), alignment));
-  auto* plainbuf = reinterpret_cast<unsigned char*>(plainnode->c_str());
-
-  for (const auto& cipherbuf : ciphertext.buffers()) {
-    // XXX: Why int?
+  // discard cached crcs as we will be writing through c_str()
+  bl.invalidate_crc();
+  for (auto& buf : bl.buffers()) {
+    auto p = reinterpret_cast<unsigned char*>(const_cast<char*>(buf.c_str()));
     int update_len = 0;
 
-    if (1 != EVP_DecryptUpdate(ectx.get(),
-       plainbuf,
-       &update_len,
-       reinterpret_cast<const unsigned char*>(cipherbuf.c_str()),
-       cipherbuf.length())) {
+    if (1 != EVP_DecryptUpdate(ectx.get(), p, &update_len, p, buf.length())) {
       throw std::runtime_error("EVP_DecryptUpdate failed");
     }
     ceph_assert_always(update_len >= 0);
-    ceph_assert(cipherbuf.length() == static_cast<unsigned>(update_len));
-
-    plainbuf += update_len;
+    ceph_assert(static_cast<unsigned>(update_len) == buf.length());
   }
-
-  ceph::bufferlist outbl;
-  outbl.push_back(std::move(plainnode));
-  return outbl;
 }
 
-
-ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update_final(
-  ceph::bufferlist&& ciphertext_and_tag,
-  std::uint32_t alignment)
+void AES128GCM_OnWireRxHandler::authenticated_decrypt_update_final(
+  ceph::bufferlist& bl)
 {
-  const auto cnt_len = ciphertext_and_tag.length();
-  ceph_assert(cnt_len >= AESGCM_TAG_LEN);
+  unsigned orig_len = bl.length();
+  ceph_assert(orig_len >= AESGCM_TAG_LEN);
 
   // decrypt optional data. Caller is obliged to provide only signature but it
   // may supply ciphertext as well. Combining the update + final is reflected
   // combined together.
-  ceph::bufferlist plainbl;
   ceph::bufferlist auth_tag;
-  {
-    const auto tag_off = cnt_len - AESGCM_TAG_LEN;
-    ceph::bufferlist ciphertext;
-    ciphertext_and_tag.splice(0, tag_off, &ciphertext);
-
-    // the rest is the signature (a.k.a auth tag)
-    auth_tag = std::move(ciphertext_and_tag);
-
-    if (ciphertext.length()) {
-      plainbl = authenticated_decrypt_update(std::move(ciphertext), alignment);
-    }
+  bl.splice(orig_len - AESGCM_TAG_LEN, AESGCM_TAG_LEN, &auth_tag);
+  if (bl.length() > 0) {
+    authenticated_decrypt_update(bl);
   }
 
   // we need to ensure the tag is stored in continuous memory.
@@ -279,23 +261,17 @@ ceph::bufferlist AES128GCM_OnWireRxHandler::authenticated_decrypt_update_final(
   {
     int final_len = 0;
     if (0 >= EVP_DecryptFinal_ex(ectx.get(), nullptr, &final_len)) {
-      ldout(cct, 15) << __func__
-                    << " plainbl.length()=" << plainbl.length()
-                    << " final_len=" << final_len
-                    << dendl;
       throw MsgAuthError();
-    } else {
-      ceph_assert_always(final_len == 0);
-      ceph_assert_always(plainbl.length() + final_len + AESGCM_TAG_LEN == cnt_len);
     }
+    ceph_assert_always(final_len == 0);
+    ceph_assert(bl.length() + AESGCM_TAG_LEN == orig_len);
   }
-
-  return plainbl;
 }
 
 ceph::crypto::onwire::rxtx_t ceph::crypto::onwire::rxtx_t::create_handler_pair(
   CephContext* cct,
   const AuthConnectionMeta& auth_meta,
+  bool new_nonce_format,
   bool crossed)
 {
   if (auth_meta.is_mode_secure()) {
@@ -323,9 +299,9 @@ ceph::crypto::onwire::rxtx_t ceph::crypto::onwire::rxtx_t::create_handler_pair(
 
     return {
       std::make_unique<AES128GCM_OnWireRxHandler>(
-       cct, key, crossed ? tx_nonce : rx_nonce),
+       cct, key, crossed ? tx_nonce : rx_nonce, new_nonce_format),
       std::make_unique<AES128GCM_OnWireTxHandler>(
-       cct, key, crossed ? rx_nonce : tx_nonce)
+       cct, key, crossed ? rx_nonce : tx_nonce, new_nonce_format)
     };
   } else {
     return { nullptr, nullptr };