]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: add data encryptor/decryptor using openssl 37083/head
authorOr Ozeri <oro@il.ibm.com>
Thu, 10 Sep 2020 08:51:56 +0000 (11:51 +0300)
committerOr Ozeri <oro@il.ibm.com>
Sun, 20 Sep 2020 18:23:45 +0000 (21:23 +0300)
This commit adds the first CryptoInterface implementation
which translates bufferlist encryption to the standard api of crypto libraries,
i.e. using block ciphers operating on C-style arrays.

It defines an abstract DataCryptor interface to allow different crypto libraries implementations.
With this commit, we add an OpenSSL based implementation.
Lastly, we implement a context-pool wrapper implementation to optimize performance.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
17 files changed:
src/librbd/CMakeLists.txt
src/librbd/crypto/BlockCrypto.cc [new file with mode: 0644]
src/librbd/crypto/BlockCrypto.h [new file with mode: 0644]
src/librbd/crypto/CryptoContextPool.cc [new file with mode: 0644]
src/librbd/crypto/CryptoContextPool.h [new file with mode: 0644]
src/librbd/crypto/CryptoInterface.h
src/librbd/crypto/CryptoObjectDispatch.cc
src/librbd/crypto/DataCryptor.h [new file with mode: 0644]
src/librbd/crypto/Types.h [new file with mode: 0644]
src/librbd/crypto/openssl/DataCryptor.cc [new file with mode: 0644]
src/librbd/crypto/openssl/DataCryptor.h [new file with mode: 0644]
src/test/librbd/CMakeLists.txt
src/test/librbd/crypto/openssl/test_DataCryptor.cc [new file with mode: 0644]
src/test/librbd/crypto/test_mock_BlockCrypto.cc [new file with mode: 0644]
src/test/librbd/crypto/test_mock_CryptoContextPool.cc [new file with mode: 0644]
src/test/librbd/mock/crypto/MockCryptoInterface.h
src/test/librbd/mock/crypto/MockDataCryptor.h [new file with mode: 0644]

index b73fa25b7622416aaabb477bb15bbd6976c6306b..9a570bcfbb9a677b5dd4f4c7b6f11f014f9227b7 100644 (file)
@@ -46,7 +46,10 @@ set(librbd_internal_srcs
   cache/rwl/InitRequest.cc
   cache/rwl/ShutdownRequest.cc
   cache/WriteAroundObjectDispatch.cc
+  crypto/BlockCrypto.cc
+  crypto/CryptoContextPool.cc
   crypto/CryptoObjectDispatch.cc
+  crypto/openssl/DataCryptor.cc
   deep_copy/ImageCopyRequest.cc
   deep_copy/MetadataCopyRequest.cc
   deep_copy/ObjectCopyRequest.cc
@@ -207,6 +210,7 @@ if(WITH_EVENTTRACE)
 endif()
 target_link_libraries(rbd_internal PRIVATE
   osdc rbd_types)
+target_include_directories(rbd_internal PRIVATE ${OPENSSL_INCLUDE_DIR})
 
 if(WITH_RBD_RWL)
   target_link_libraries(rbd_internal
diff --git a/src/librbd/crypto/BlockCrypto.cc b/src/librbd/crypto/BlockCrypto.cc
new file mode 100644 (file)
index 0000000..00b244c
--- /dev/null
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/BlockCrypto.h"
+#include <alloca.h>
+#include "include/byteorder.h"
+#include "include/ceph_assert.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+BlockCrypto<T>::BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
+                            uint32_t block_size)
+     : m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_size),
+       m_iv_size(data_cryptor->get_iv_size()) {
+  ceph_assert((block_size % data_cryptor->get_block_size()) == 0);
+}
+
+template <typename T>
+int BlockCrypto<T>::crypt(ceph::bufferlist* data, uint64_t image_offset,
+                           CipherMode mode) {
+  if (image_offset % m_block_size != 0) {
+    lderr(m_cct) << "image offset: " << image_offset
+                 << " not aligned to block size: " << m_block_size << dendl;
+    return -EINVAL;
+  }
+  if (data->length() % m_block_size != 0) {
+    lderr(m_cct) << "data length: " << data->length()
+                 << " not aligned to block size: " << m_block_size << dendl;
+    return -EINVAL;
+  }
+
+  unsigned char* iv = (unsigned char*)alloca(m_iv_size);
+  memset(iv, 0, m_iv_size);
+
+  bufferlist src = *data;
+  data->clear();
+
+  auto ctx = m_data_cryptor->get_context(mode);
+  if (ctx == nullptr) {
+    lderr(m_cct) << "unable to get crypt context" << dendl;
+    return -EIO;
+  }
+  auto block_offset = image_offset / m_block_size;
+  auto appender = data->get_contiguous_appender(src.length());
+  unsigned char* out_buf_ptr = nullptr;
+  uint32_t remaining_block_bytes = 0;
+  for (auto buf = src.buffers().begin(); buf != src.buffers().end(); ++buf) {
+    auto in_buf_ptr = reinterpret_cast<const unsigned char*>(buf->c_str());
+    auto remaining_buf_bytes = buf->length();
+    while (remaining_buf_bytes > 0) {
+      if (remaining_block_bytes == 0) {
+        auto block_offset_le = init_le64(block_offset);
+        memcpy(iv, &block_offset_le, sizeof(block_offset_le));
+        auto r = m_data_cryptor->init_context(ctx, iv, m_iv_size);
+        if (r != 0) {
+          lderr(m_cct) << "unable to init cipher's IV" << dendl;
+          return r;
+        }
+
+        out_buf_ptr = reinterpret_cast<unsigned char*>(
+                appender.get_pos_add(m_block_size));
+        remaining_block_bytes = m_block_size;
+        ++block_offset;
+      }
+
+      auto crypto_input_length = std::min(remaining_buf_bytes,
+                                          remaining_block_bytes);
+      auto crypto_output_length = m_data_cryptor->update_context(
+              ctx, in_buf_ptr, out_buf_ptr, crypto_input_length);
+      if (crypto_output_length < 0) {
+        lderr(m_cct) << "crypt update failed" << dendl;
+        return crypto_output_length;
+      }
+
+      out_buf_ptr += crypto_output_length;
+      in_buf_ptr += crypto_input_length;
+      remaining_buf_bytes -= crypto_input_length;
+      remaining_block_bytes -= crypto_input_length;
+    }
+  }
+
+  m_data_cryptor->return_context(ctx, mode);
+
+  return 0;
+}
+
+template <typename T>
+int BlockCrypto<T>::encrypt(ceph::bufferlist* data, uint64_t image_offset) {
+  return crypt(data, image_offset, CipherMode::CIPHER_MODE_ENC);
+}
+
+template <typename T>
+int BlockCrypto<T>::decrypt(ceph::bufferlist* data, uint64_t image_offset) {
+  return crypt(data, image_offset, CipherMode::CIPHER_MODE_DEC);
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/BlockCrypto.h b/src/librbd/crypto/BlockCrypto.h
new file mode 100644 (file)
index 0000000..fa51a61
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
+#define CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
+
+#include "include/Context.h"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/crypto/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+class BlockCrypto : public CryptoInterface {
+
+public:
+    BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
+                uint32_t block_size);
+
+    int encrypt(ceph::bufferlist* data, uint64_t image_offset) override;
+    int decrypt(ceph::bufferlist* data, uint64_t image_offset) override;
+
+    uint32_t get_block_size() const {
+      return m_block_size;
+    }
+
+private:
+    CephContext* m_cct;
+    DataCryptor<T>* m_data_cryptor;
+    uint32_t m_block_size;
+    uint32_t m_iv_size;
+
+    int crypt(ceph::bufferlist* data, uint64_t image_offset, CipherMode mode);
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif //CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
diff --git a/src/librbd/crypto/CryptoContextPool.cc b/src/librbd/crypto/CryptoContextPool.cc
new file mode 100644 (file)
index 0000000..b303a54
--- /dev/null
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoContextPool.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+CryptoContextPool<T>::CryptoContextPool(DataCryptor<T>* data_cryptor,
+                                        uint32_t pool_size)
+     : m_data_cryptor(data_cryptor), m_encrypt_contexts(pool_size),
+       m_decrypt_contexts(pool_size) {
+}
+
+template <typename T>
+CryptoContextPool<T>::~CryptoContextPool() {
+  T* ctx;
+  while (m_encrypt_contexts.pop(ctx)) {
+    m_data_cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+  }
+  while (m_decrypt_contexts.pop(ctx)) {
+    m_data_cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+  }
+}
+
+template <typename T>
+T* CryptoContextPool<T>::get_context(CipherMode mode) {
+  T* ctx;
+  if (!get_contexts(mode).pop(ctx)) {
+    ctx = m_data_cryptor->get_context(mode);
+  }
+  return ctx;
+}
+
+template <typename T>
+void CryptoContextPool<T>::return_context(T* ctx, CipherMode mode) {
+  if (!get_contexts(mode).push(ctx)) {
+    m_data_cryptor->return_context(ctx, mode);
+  }
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/CryptoContextPool.h b/src/librbd/crypto/CryptoContextPool.h
new file mode 100644 (file)
index 0000000..ce5abfa
--- /dev/null
@@ -0,0 +1,65 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_CONTEXT_POOL_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_CONTEXT_POOL_H
+
+#include "librbd/crypto/DataCryptor.h"
+#include "common/allocator.h"
+#include "include/ceph_assert.h"
+#include <boost/lockfree/queue.hpp>
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+class CryptoContextPool : public DataCryptor<T>  {
+
+public:
+    CryptoContextPool(DataCryptor<T>* data_cryptor, uint32_t pool_size);
+    ~CryptoContextPool();
+
+    T* get_context(CipherMode mode) override;
+    void return_context(T* ctx, CipherMode mode) override;
+
+    inline uint32_t get_block_size() const override {
+      return m_data_cryptor->get_block_size();
+    }
+    inline uint32_t get_iv_size() const override {
+      return m_data_cryptor->get_iv_size();
+    }
+    inline int init_context(T* ctx, const unsigned char* iv,
+                            uint32_t iv_length) const override {
+      return m_data_cryptor->init_context(ctx, iv, iv_length);
+    }
+    inline int update_context(T* ctx, const unsigned char* in,
+                              unsigned char* out,
+                              uint32_t len) const override {
+      return m_data_cryptor->update_context(ctx, in, out, len);
+    }
+
+    typedef boost::lockfree::queue<
+            T*,
+            boost::lockfree::allocator<ceph::allocator<void>>> ContextQueue;
+
+private:
+    DataCryptor<T>* m_data_cryptor;
+    ContextQueue m_encrypt_contexts;
+    ContextQueue m_decrypt_contexts;
+
+    inline ContextQueue& get_contexts(CipherMode mode) {
+      switch(mode) {
+        case CIPHER_MODE_ENC:
+          return m_encrypt_contexts;
+        case CIPHER_MODE_DEC:
+          return m_decrypt_contexts;
+        default:
+          ceph_assert(false);
+      }
+    }
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_CONTEXT_POOL_H
index 28f2baacc3894fc46995a8afc5efd1fc508b7fa4..e0e4f9f56272bd9a81e704d56e08e272e0309b75 100644 (file)
@@ -13,10 +13,8 @@ namespace crypto {
 class CryptoInterface : public RefCountedObject {
 
 public:
-  virtual void encrypt(ceph::bufferlist&& data,
-                       uint64_t image_offset) const = 0;
-  virtual void decrypt(ceph::bufferlist&& data,
-                       uint64_t image_offset) const = 0;
+  virtual int encrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
+  virtual int decrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
 
 };
 
index 12cc7e899ad6575b0737a835b0a4254836c96fa0..eaad8dea6e0667f73f71ddcfe3ca61ec11f73015 100644 (file)
@@ -72,11 +72,15 @@ struct C_EncryptedObjectReadRequest : public Context {
     void finish(int r) override {
       ldout(image_ctx->cct, 20) << "r=" << r << dendl;
       if (r > 0) {
-        crypto->decrypt(
-                std::move(*read_data),
+        auto crypto_ret = crypto->decrypt(
+                read_data,
                 Striper::get_file_offset(
                         image_ctx->cct, &image_ctx->layout, object_no,
                         object_off));
+        if (crypto_ret != 0) {
+          ceph_assert(crypto_ret < 0);
+          r = crypto_ret;
+        }
       }
       onfinish->complete(r);
     }
@@ -151,13 +155,13 @@ bool CryptoObjectDispatch<I>::write(
                  << object_off << "~" << data.length() << dendl;
   ceph_assert(m_crypto != nullptr);
 
-  m_crypto->encrypt(
-          std::move(data),
+  auto r = m_crypto->encrypt(
+          &data,
           Striper::get_file_offset(
                   m_image_ctx->cct, &m_image_ctx->layout, object_no,
                   object_off));
   *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
-  on_dispatched->complete(0);
+  on_dispatched->complete(r);
   return true;
 }
 
@@ -211,10 +215,12 @@ bool CryptoObjectDispatch<I>::compare_and_write(
 
   uint64_t image_offset = Striper::get_file_offset(
           m_image_ctx->cct, &m_image_ctx->layout, object_no, object_off);
-  m_crypto->encrypt(std::move(cmp_data), image_offset);
-  m_crypto->encrypt(std::move(write_data), image_offset);
+  auto r = m_crypto->encrypt(&cmp_data, image_offset);
+  if (r == 0) {
+    r = m_crypto->encrypt(&write_data, image_offset);
+  }
   *dispatch_result = io::DISPATCH_RESULT_CONTINUE;
-  on_dispatched->complete(0);
+  on_dispatched->complete(r);
   return true;
 }
 
diff --git a/src/librbd/crypto/DataCryptor.h b/src/librbd/crypto/DataCryptor.h
new file mode 100644 (file)
index 0000000..cf29385
--- /dev/null
@@ -0,0 +1,34 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
+#define CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
+
+#include "include/int_types.h"
+#include "librbd/crypto/Types.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+class DataCryptor {
+
+public:
+
+  virtual ~DataCryptor() = default;
+
+  virtual uint32_t get_block_size() const = 0;
+  virtual uint32_t get_iv_size() const = 0;
+  virtual T* get_context(CipherMode mode) = 0;
+  virtual void return_context(T* ctx, CipherMode mode) = 0;
+
+  virtual int init_context(T* ctx, const unsigned char* iv,
+                           uint32_t iv_length) const = 0;
+  virtual int update_context(T* ctx, const unsigned char* in,
+                             unsigned char* out, uint32_t len) const = 0;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
diff --git a/src/librbd/crypto/Types.h b/src/librbd/crypto/Types.h
new file mode 100644 (file)
index 0000000..93d9c17
--- /dev/null
@@ -0,0 +1,18 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_TYPES_H
+#define CEPH_LIBRBD_CRYPTO_TYPES_H
+
+namespace librbd {
+namespace crypto {
+
+enum CipherMode {
+    CIPHER_MODE_ENC,
+    CIPHER_MODE_DEC,
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
diff --git a/src/librbd/crypto/openssl/DataCryptor.cc b/src/librbd/crypto/openssl/DataCryptor.cc
new file mode 100644 (file)
index 0000000..4394b17
--- /dev/null
@@ -0,0 +1,137 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/openssl/DataCryptor.h"
+#include <openssl/err.h>
+#include <string.h>
+#include "include/ceph_assert.h"
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+int DataCryptor::init(const char* cipher_name, const unsigned char* key,
+                      uint16_t key_length) {
+  m_key = nullptr;
+  if (cipher_name == nullptr) {
+    lderr(m_cct) << "missing cipher name" << dendl;
+    return -EINVAL;
+  }
+  if (key == nullptr) {
+    lderr(m_cct) << "missing key" << dendl;
+    return -EINVAL;
+  }
+
+  m_cipher = EVP_get_cipherbyname(cipher_name);
+  if (m_cipher == nullptr) {
+    lderr(m_cct) << "EVP_get_cipherbyname failed. Cipher name: " << cipher_name
+                 << dendl;
+    log_errors();
+    return -EINVAL;
+  }
+
+  auto expected_key_length = EVP_CIPHER_key_length(m_cipher);
+  if (expected_key_length != key_length) {
+    lderr(m_cct) << "cipher " << cipher_name << " expects key of "
+                 << expected_key_length << " bytes. got: " << key_length
+                 << dendl;
+    return -EINVAL;
+  }
+
+  m_key = new unsigned char[key_length];
+  memcpy(m_key, key, key_length);
+  m_iv_size = static_cast<uint32_t>(EVP_CIPHER_iv_length(m_cipher));
+  return 0;
+}
+
+DataCryptor::~DataCryptor() {
+  if (m_key != nullptr) {
+    explicit_bzero(m_key, EVP_CIPHER_key_length(m_cipher));
+    delete m_key;
+    m_key = nullptr;
+  }
+}
+
+uint32_t DataCryptor::get_block_size() const {
+  return EVP_CIPHER_block_size(m_cipher);
+}
+
+uint32_t DataCryptor::get_iv_size() const {
+  return m_iv_size;
+}
+
+EVP_CIPHER_CTX* DataCryptor::get_context(CipherMode mode) {
+  int enc;
+  switch(mode) {
+    case CIPHER_MODE_ENC:
+      enc = 1;
+      break;
+    case CIPHER_MODE_DEC:
+      enc = 0;
+      break;
+    default:
+      lderr(m_cct) << "Invalid CipherMode:" << mode << dendl;
+      return nullptr;
+  }
+
+  auto ctx = EVP_CIPHER_CTX_new();
+  if (ctx == nullptr) {
+    lderr(m_cct) << "EVP_CIPHER_CTX_new failed" << dendl;
+    log_errors();
+    return nullptr;
+  }
+
+  if (1 != EVP_CipherInit_ex(ctx, m_cipher, nullptr, m_key, nullptr, enc)) {
+    lderr(m_cct) << "EVP_CipherInit_ex failed" << dendl;
+    log_errors();
+    return nullptr;
+  }
+
+  return ctx;
+}
+
+void DataCryptor::return_context(EVP_CIPHER_CTX* ctx, CipherMode mode) {
+  if (ctx != nullptr) {
+    EVP_CIPHER_CTX_free(ctx);
+  }
+}
+
+int DataCryptor::init_context(EVP_CIPHER_CTX* ctx, const unsigned char* iv,
+                              uint32_t iv_length) const {
+  if (iv_length != m_iv_size) {
+    lderr(m_cct) << "cipher expects IV of " << m_iv_size << " bytes. got: "
+                 << iv_length << dendl;
+    return -EINVAL;
+  }
+  if (1 != EVP_CipherInit_ex(ctx, nullptr, nullptr, nullptr, iv, -1)) {
+    lderr(m_cct) << "EVP_CipherInit_ex failed" << dendl;
+    log_errors();
+    return -EIO;
+  }
+  return 0;
+}
+
+int DataCryptor::update_context(EVP_CIPHER_CTX* ctx, const unsigned char* in,
+                                unsigned char* out, uint32_t len) const {
+  int out_length;
+  if (1 != EVP_CipherUpdate(ctx, out, &out_length, in, len)) {
+    lderr(m_cct) << "EVP_CipherUpdate failed. len=" << len << dendl;
+    log_errors();
+    return -EIO;
+  }
+  return out_length;
+}
+
+void DataCryptor::log_errors() const {
+  while (true) {
+    auto error = ERR_get_error();
+    if (error == 0) {
+      break;
+    }
+    lderr(m_cct) << "OpenSSL error: " << error << dendl;
+  }
+}
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/openssl/DataCryptor.h b/src/librbd/crypto/openssl/DataCryptor.h
new file mode 100644 (file)
index 0000000..0c2795e
--- /dev/null
@@ -0,0 +1,45 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H
+#define CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H
+
+#include "librbd/crypto/DataCryptor.h"
+#include "include/Context.h"
+#include <openssl/evp.h>
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+class DataCryptor : public crypto::DataCryptor<EVP_CIPHER_CTX> {
+
+public:
+    DataCryptor(CephContext* cct) : m_cct(cct) {};
+    ~DataCryptor();
+
+    int init(const char* cipher_name, const unsigned char* key,
+             uint16_t key_length);
+    uint32_t get_block_size() const override;
+    uint32_t get_iv_size() const override;
+    EVP_CIPHER_CTX* get_context(CipherMode mode) override;
+    void return_context(EVP_CIPHER_CTX* ctx, CipherMode mode) override;
+    int init_context(EVP_CIPHER_CTX* ctx, const unsigned char* iv,
+                     uint32_t iv_length) const override;
+    int update_context(EVP_CIPHER_CTX* ctx, const unsigned char* in,
+                       unsigned char* out, uint32_t len) const override;
+
+private:
+    CephContext* m_cct;
+    unsigned char* m_key = nullptr;
+    const EVP_CIPHER* m_cipher;
+    uint32_t m_iv_size;
+
+    void log_errors() const;
+};
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H
index ae6966d5daf95410780da5b1da1a25d4fa2092ea..0230e2e2918fe8f921af2873573166dabd710b45 100644 (file)
@@ -53,7 +53,10 @@ set(unittest_librbd_srcs
   test_mock_Watcher.cc
   cache/test_mock_WriteAroundObjectDispatch.cc
   cache/test_mock_ParentCacheObjectDispatch.cc
+  crypto/test_mock_BlockCrypto.cc
+  crypto/test_mock_CryptoContextPool.cc
   crypto/test_mock_CryptoObjectDispatch.cc
+  crypto/openssl/test_DataCryptor.cc
   deep_copy/test_mock_ImageCopyRequest.cc
   deep_copy/test_mock_MetadataCopyRequest.cc
   deep_copy/test_mock_ObjectCopyRequest.cc
diff --git a/src/test/librbd/crypto/openssl/test_DataCryptor.cc b/src/test/librbd/crypto/openssl/test_DataCryptor.cc
new file mode 100644 (file)
index 0000000..16c144e
--- /dev/null
@@ -0,0 +1,117 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_fixture.h"
+#include "librbd/crypto/openssl/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+const char* TEST_CIPHER_NAME = "aes-256-xts";
+const unsigned char TEST_KEY[64] = {1};
+const unsigned char TEST_IV[16] = {2};
+const unsigned char TEST_IV_2[16] = {3};
+const unsigned char TEST_DATA[4096] = {4};
+
+struct TestDataCryptor : public TestFixture {
+    DataCryptor *cryptor;
+
+    void SetUp() override {
+      TestFixture::SetUp();
+      cryptor = new DataCryptor(reinterpret_cast<CephContext*>(m_ioctx.cct()));
+      ASSERT_EQ(0,
+                cryptor->init(TEST_CIPHER_NAME, TEST_KEY, sizeof(TEST_KEY)));
+    }
+
+    void TearDown() override {
+      delete cryptor;
+      Test::TearDown();
+    }
+};
+
+TEST_F(TestDataCryptor, InvalidCipherName) {
+  EXPECT_EQ(-EINVAL, cryptor->init(nullptr, TEST_KEY, sizeof(TEST_KEY)));
+  EXPECT_EQ(-EINVAL, cryptor->init("", TEST_KEY, sizeof(TEST_KEY)));
+  EXPECT_EQ(-EINVAL, cryptor->init("Invalid", TEST_KEY, sizeof(TEST_KEY)));
+}
+
+TEST_F(TestDataCryptor, InvalidKey) {
+  EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, 0));
+  EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr,
+                                   sizeof(TEST_KEY)));
+  EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, TEST_KEY, 1));
+}
+
+TEST_F(TestDataCryptor, GetContextInvalidMode) {
+  EXPECT_EQ(nullptr, cryptor->get_context(static_cast<CipherMode>(-1)));
+}
+
+TEST_F(TestDataCryptor, ReturnNullContext) {
+  cryptor->return_context(nullptr, static_cast<CipherMode>(-1));
+}
+
+TEST_F(TestDataCryptor, ReturnContextInvalidMode) {
+  auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx, nullptr);
+  cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+  ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx, nullptr);
+  cryptor->return_context(ctx, static_cast<CipherMode>(-1));
+}
+
+TEST_F(TestDataCryptor, EncryptDecrypt) {
+  auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx, nullptr);
+  cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV));
+
+  unsigned char out[sizeof(TEST_DATA)];
+  ASSERT_EQ(sizeof(TEST_DATA),
+            cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+  cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+  ctx = cryptor->get_context(CipherMode::CIPHER_MODE_DEC);
+  ASSERT_NE(ctx, nullptr);
+  ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)));
+  ASSERT_EQ(sizeof(TEST_DATA),
+            cryptor->update_context(ctx, out, out, sizeof(TEST_DATA)));
+  ASSERT_EQ(0, memcmp(out, TEST_DATA, sizeof(TEST_DATA)));
+  cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+}
+
+TEST_F(TestDataCryptor, ReuseContext) {
+  auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx, nullptr);
+
+  ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)));
+  unsigned char out[sizeof(TEST_DATA)];
+  ASSERT_EQ(sizeof(TEST_DATA),
+            cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+
+  ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV_2, sizeof(TEST_IV_2)));
+  ASSERT_EQ(sizeof(TEST_DATA),
+            cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA)));
+
+  auto ctx2 = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx2, nullptr);
+
+  ASSERT_EQ(0, cryptor->init_context(ctx2, TEST_IV_2, sizeof(TEST_IV_2)));
+  unsigned char out2[sizeof(TEST_DATA)];
+  ASSERT_EQ(sizeof(TEST_DATA),
+            cryptor->update_context(ctx2, TEST_DATA, out2, sizeof(TEST_DATA)));
+
+  ASSERT_EQ(0, memcmp(out, out2, sizeof(TEST_DATA)));
+
+  cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+  cryptor->return_context(ctx2, CipherMode::CIPHER_MODE_ENC);
+}
+
+TEST_F(TestDataCryptor, InvalidIVLength) {
+  auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC);
+  ASSERT_NE(ctx, nullptr);
+
+  ASSERT_EQ(-EINVAL, cryptor->init_context(ctx, TEST_IV, 1));
+}
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_BlockCrypto.cc b/src/test/librbd/crypto/test_mock_BlockCrypto.cc
new file mode 100644 (file)
index 0000000..7251016
--- /dev/null
@@ -0,0 +1,144 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_fixture.h"
+#include "librbd/crypto/BlockCrypto.h"
+#include "test/librbd/mock/crypto/MockDataCryptor.h"
+
+#include "librbd/crypto/BlockCrypto.cc"
+template class librbd::crypto::BlockCrypto<
+        librbd::crypto::MockCryptoContext>;
+
+using ::testing::ExpectationSet;
+using ::testing::internal::ExpectationBase;
+using ::testing::Return;
+using ::testing::_;
+
+namespace librbd {
+namespace crypto {
+
+MATCHER_P(CompareArrayToString, s, "") {
+  return (memcmp(arg, s.c_str(), s.length()) == 0);
+}
+
+struct TestMockBlockCrypto : public TestFixture {
+    MockDataCryptor cryptor;
+    BlockCrypto<MockCryptoContext>* bc;
+    int cryptor_block_size = 2;
+    int cryptor_iv_size = 16;
+    int block_size = 4;
+    ExpectationSet* expectation_set;
+
+    void SetUp() override {
+      TestFixture::SetUp();
+
+      cryptor.block_size = cryptor_block_size;
+      bc = new BlockCrypto<MockCryptoContext>(
+              reinterpret_cast<CephContext*>(m_ioctx.cct()), &cryptor,
+              block_size);
+      expectation_set = new ExpectationSet();
+    }
+
+    void TearDown() override {
+      delete expectation_set;
+      TestFixture::TearDown();
+    }
+
+    void expect_get_context(CipherMode mode) {
+      _set_last_expectation(
+              EXPECT_CALL(cryptor, get_context(mode))
+              .After(*expectation_set).WillOnce(Return(
+                      new MockCryptoContext())));
+    }
+
+    void expect_init_context(const std::string& iv) {
+      _set_last_expectation(
+              EXPECT_CALL(cryptor, init_context(_, CompareArrayToString(iv),
+                                                cryptor_iv_size))
+              .After(*expectation_set));
+    }
+
+    void expect_update_context(const std::string& in_str, int out_ret) {
+      _set_last_expectation(
+              EXPECT_CALL(cryptor, update_context(_,
+                                                  CompareArrayToString(in_str),
+                                                  _, in_str.length()))
+              .After(*expectation_set).WillOnce(Return(out_ret)));
+    }
+
+    void _set_last_expectation(ExpectationBase& expectation) {
+      delete expectation_set;
+      expectation_set = new ExpectationSet(expectation);
+    }
+};
+
+TEST_F(TestMockBlockCrypto, Encrypt) {
+  uint32_t image_offset = 0x1234 * block_size;
+
+  ceph::bufferlist data1;
+  data1.append("123");
+  ceph::bufferlist data2;
+  data2.append("456");
+  ceph::bufferlist data3;
+  data3.append("78");
+
+  // bufferlist buffers: "123", "456", "78"
+  ceph::bufferlist data;
+  data.claim_append(data1);
+  data.claim_append(data2);
+  data.claim_append(data3);
+
+  expect_get_context(CipherMode::CIPHER_MODE_ENC);
+  expect_init_context(std::string("\x34\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16));
+  expect_update_context("123", 0);
+  expect_update_context("4", 4);
+  expect_init_context(std::string("\x35\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16));
+  expect_update_context("56", 0);
+  expect_update_context("78", 4);
+  EXPECT_CALL(cryptor, return_context(_, CipherMode::CIPHER_MODE_ENC));
+
+  ASSERT_EQ(0, bc->encrypt(&data, image_offset));
+
+  ASSERT_EQ(data.length(), 8);
+  ASSERT_TRUE(data.is_aligned(block_size));
+}
+
+TEST_F(TestMockBlockCrypto, UnalignedImageOffset) {
+  ceph::bufferlist data;
+  data.append("1234");
+  ASSERT_EQ(-EINVAL, bc->encrypt(&data, 2));
+}
+
+TEST_F(TestMockBlockCrypto, UnalignedDataLength) {
+  ceph::bufferlist data;
+  data.append("123");
+  ASSERT_EQ(-EINVAL, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockBlockCrypto, GetContextError) {
+  ceph::bufferlist data;
+  data.append("1234");
+  EXPECT_CALL(cryptor, get_context(CipherMode::CIPHER_MODE_ENC)).WillOnce(
+          Return(nullptr));
+  ASSERT_EQ(-EIO, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockBlockCrypto, InitContextError) {
+  ceph::bufferlist data;
+  data.append("1234");
+  expect_get_context(CipherMode::CIPHER_MODE_ENC);
+  EXPECT_CALL(cryptor, init_context(_, _, _)).WillOnce(Return(-123));
+  ASSERT_EQ(-123, bc->encrypt(&data, 0));
+}
+
+TEST_F(TestMockBlockCrypto, UpdateContextError) {
+  ceph::bufferlist data;
+  data.append("1234");
+  expect_get_context(CipherMode::CIPHER_MODE_ENC);
+  EXPECT_CALL(cryptor, init_context(_, _, _));
+  EXPECT_CALL(cryptor, update_context(_, _, _, _)).WillOnce(Return(-123));
+  ASSERT_EQ(-123, bc->encrypt(&data, 0));
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/test_mock_CryptoContextPool.cc b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc
new file mode 100644 (file)
index 0000000..1d0402d
--- /dev/null
@@ -0,0 +1,54 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "gtest/gtest.h"
+#include "librbd/crypto/CryptoContextPool.h"
+#include "test/librbd/mock/crypto/MockDataCryptor.h"
+
+#include "librbd/crypto/CryptoContextPool.cc"
+template class librbd::crypto::CryptoContextPool<
+        librbd::crypto::MockCryptoContext>;
+
+using ::testing::Return;
+
+namespace librbd {
+namespace crypto {
+
+struct TestMockCryptoContextPool : public ::testing::Test {
+    MockDataCryptor cryptor;
+
+    void expect_get_context(CipherMode mode) {
+      EXPECT_CALL(cryptor, get_context(mode)).WillOnce(Return(
+              new MockCryptoContext()));
+    }
+
+    void expect_return_context(MockCryptoContext* ctx, CipherMode mode) {
+      delete ctx;
+      EXPECT_CALL(cryptor, return_context(ctx, mode));
+    }
+};
+
+TEST_F(TestMockCryptoContextPool, Test) {
+  CryptoContextPool<MockCryptoContext> pool(&cryptor, 1);
+
+  expect_get_context(CipherMode::CIPHER_MODE_ENC);
+  auto enc_ctx = pool.get_context(CipherMode::CIPHER_MODE_ENC);
+
+  expect_get_context(CipherMode::CIPHER_MODE_DEC);
+  auto dec_ctx1 = pool.get_context(CipherMode::CIPHER_MODE_DEC);
+  expect_get_context(CipherMode::CIPHER_MODE_DEC);
+  auto dec_ctx2 = pool.get_context(CipherMode::CIPHER_MODE_DEC);
+  pool.return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC);
+  expect_return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC);
+  pool.return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC);
+
+  pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+  ASSERT_EQ(enc_ctx, pool.get_context(CipherMode::CIPHER_MODE_ENC));
+  pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+
+  expect_return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC);
+  expect_return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC);
+}
+
+} // namespace crypto
+} // namespace librbd
index 5bb1c7e31c8b04ce3357c490b9708c25c2ea0f81..7c5869226d9251ac69ef0e7881c43bb1b354d2e3 100644 (file)
@@ -13,8 +13,8 @@ namespace crypto {
 
 struct MockCryptoInterface : CryptoInterface {
 
-  MOCK_CONST_METHOD2(encrypt, void(ceph::bufferlist&&, uint64_t));
-  MOCK_CONST_METHOD2(decrypt, void(ceph::bufferlist&&, uint64_t));
+  MOCK_METHOD2(encrypt, int(ceph::bufferlist*, uint64_t));
+  MOCK_METHOD2(decrypt, int(ceph::bufferlist*, uint64_t));
 };
 
 } // namespace crypto
diff --git a/src/test/librbd/mock/crypto/MockDataCryptor.h b/src/test/librbd/mock/crypto/MockDataCryptor.h
new file mode 100644 (file)
index 0000000..2f0198b
--- /dev/null
@@ -0,0 +1,41 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H
+#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H
+
+#include "gmock/gmock.h"
+#include "librbd/crypto/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+
+struct MockCryptoContext {};
+
+class MockDataCryptor : public DataCryptor<MockCryptoContext> {
+
+public:
+  uint32_t block_size = 16;
+  uint32_t iv_size = 16;
+
+  uint32_t get_block_size() const override {
+    return block_size;
+  }
+
+  uint32_t get_iv_size() const override {
+    return iv_size;
+  }
+
+  MOCK_METHOD1(get_context, MockCryptoContext*(CipherMode));
+  MOCK_METHOD2(return_context, void(MockCryptoContext*, CipherMode));
+  MOCK_CONST_METHOD3(init_context, int(MockCryptoContext*,
+                                       const unsigned char*, uint32_t));
+  MOCK_CONST_METHOD4(update_context, int(MockCryptoContext*,
+                                         const unsigned char*, unsigned char*,
+                                         uint32_t));
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H