]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
librbd: add LUKS support
authorOr Ozeri <oro@il.ibm.com>
Wed, 28 Oct 2020 12:22:06 +0000 (14:22 +0200)
committerJason Dillaman <dillaman@redhat.com>
Mon, 23 Nov 2020 15:32:14 +0000 (10:32 -0500)
This commit introduces internal (not yet part of the api) librbd functions for:
1. formating an RBD image in LUKS format
2. parsing an RBD image in LUKS format

 The actual implementation of the LUKS format is done via libcryptsetup, which is added as a new dependency.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
25 files changed:
CMakeLists.txt
ceph.spec.in
cmake/modules/Findlibcryptsetup.cmake [new file with mode: 0644]
debian/control
src/include/config-h.in.cmake
src/librbd/CMakeLists.txt
src/librbd/crypto/BlockCrypto.cc
src/librbd/crypto/BlockCrypto.h
src/librbd/crypto/CryptoInterface.h
src/librbd/crypto/Types.h
src/librbd/crypto/luks/FormatRequest.cc [new file with mode: 0644]
src/librbd/crypto/luks/FormatRequest.h [new file with mode: 0644]
src/librbd/crypto/luks/Header.cc [new file with mode: 0644]
src/librbd/crypto/luks/Header.h [new file with mode: 0644]
src/librbd/crypto/luks/LoadRequest.cc [new file with mode: 0644]
src/librbd/crypto/luks/LoadRequest.h [new file with mode: 0644]
src/librbd/io/Dispatcher.h
src/librbd/io/DispatcherInterface.h
src/test/librbd/CMakeLists.txt
src/test/librbd/crypto/luks/test_mock_FormatRequest.cc [new file with mode: 0644]
src/test/librbd/crypto/luks/test_mock_LoadRequest.cc [new file with mode: 0644]
src/test/librbd/crypto/test_mock_BlockCrypto.cc
src/test/librbd/mock/crypto/MockCryptoInterface.h
src/test/librbd/mock/io/MockImageDispatcher.h
src/test/librbd/mock/io/MockObjectDispatcher.h

index a5c560fee6d9d865af0de9f0881fb7dedfe9adfe..5357a2143c418ca055ed659fd59a3dee1387be5d 100644 (file)
@@ -193,6 +193,15 @@ if(WITH_BLUESTORE)
   endif()
 endif()
 
+# libcryptsetup is only available on linux
+if(WITH_RBD AND LINUX)
+  find_package(libcryptsetup REQUIRED)
+  set(HAVE_LIBCRYPTSETUP ${LIBCRYPTSETUP_FOUND})
+  if(${LIBCRYPTSETUP_VERSION} VERSION_LESS 2.0.5)
+    set(LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT TRUE)
+  endif()
+endif()
+
 include(CMakeDependentOption)
 
 CMAKE_DEPENDENT_OPTION(WITH_ZBD "Enable libzbd bluestore backend" OFF
index 81258139d1a0e45cff9287124207dbe5b80ce8d9..9ac67112f4860d7427c79283eef44e7e81dea0c9 100644 (file)
@@ -152,6 +152,7 @@ BuildRequires:      gperftools-devel >= 2.4
 BuildRequires: leveldb-devel > 1.2
 BuildRequires: libaio-devel
 BuildRequires: libblkid-devel >= 2.17
+BuildRequires: cryptsetup-devel
 BuildRequires: libcurl-devel
 BuildRequires: libcap-ng-devel
 BuildRequires: fmt-devel >= 5.2.1
diff --git a/cmake/modules/Findlibcryptsetup.cmake b/cmake/modules/Findlibcryptsetup.cmake
new file mode 100644 (file)
index 0000000..98b3338
--- /dev/null
@@ -0,0 +1,32 @@
+# - Find libcryptsetup
+# Sets the following:
+#
+# LIBCRYPTSETUP_INCLUDE_DIR
+# LIBCRYPTSETUP_LIBRARIES
+# LIBCRYPTSETUP_VERSION
+# LIBCRYPTSETUP_FOUND
+
+pkg_search_module(PC_libcryptsetup libcryptsetup)
+
+find_path(LIBCRYPTSETUP_INCLUDE_DIR
+        NAMES libcryptsetup.h
+        PATHS ${PC_libcryptsetup_INCLUDE_DIRS})
+
+find_library(LIBCRYPTSETUP_LIBRARIES
+        NAMES libcryptsetup.so
+        PATHS ${PC_libcryptsetup_LIBRARY_DIRS})
+
+set(LIBCRYPTSETUP_VERSION ${PC_libcryptsetup_VERSION})
+
+include(FindPackageHandleStandardArgs)
+
+find_package_handle_standard_args(libcryptsetup
+        REQUIRED_VARS
+        LIBCRYPTSETUP_INCLUDE_DIR
+        LIBCRYPTSETUP_LIBRARIES
+        VERSION_VAR LIBCRYPTSETUP_VERSION)
+
+mark_as_advanced(
+  LIBCRYPTSETUP_LIBRARIES
+  LIBCRYPTSETUP_INCLUDE_DIR
+  LIBCRYPTSETUP_VERSION)
index 13471e67c0ef36f757a297ad58f9fcc72a54a4d4..1e22cf916433e5af33d3fee8f84422e0b4d3c042 100644 (file)
@@ -29,6 +29,7 @@ Build-Depends: cmake (>= 3.10.2),
                libblkid-dev (>= 2.17),
 # Crimson      libc-ares-dev,
 # Crimson      libcrypto++-dev,
+               libcryptsetup-dev,
                libcap-ng-dev,
                libcunit1-dev,
                libcurl4-openssl-dev,
index f3931951322e180982ce8d7e89555e86cfc2ba27..d952983c55b3b85c6420b18b97a134f50a14937a 100644 (file)
 /* Define if PWL-SSD is enabled */
 #cmakedefine WITH_RBD_SSD_CACHE
 
+/* Define if libcryptsetup version < 2.0.5 */
+#cmakedefine LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT
+
 /* Shared library extension, such as .so, .dll or .dylib */
 #cmakedefine CMAKE_SHARED_LIBRARY_SUFFIX "@CMAKE_SHARED_LIBRARY_SUFFIX@"
 
index 6f7977fdae5b9a161b0be5494fdf31f54c47fbff..0c36cc79816b447d84dfb7bfd76e034e3bda04e0 100644 (file)
@@ -200,6 +200,13 @@ if(WITH_EVENTTRACE)
   list(APPEND librbd_internal_srcs ../common/EventTrace.cc)
 endif()
 
+if(LINUX AND HAVE_LIBCRYPTSETUP)
+  list(APPEND librbd_internal_srcs
+          crypto/luks/Header.cc
+          crypto/luks/FormatRequest.cc
+          crypto/luks/LoadRequest.cc)
+endif()
+
 if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
   set(librbd_internal_srcs
     ${librbd_internal_srcs}
@@ -241,6 +248,10 @@ target_link_libraries(rbd_internal PRIVATE
   osdc rbd_types
   OpenSSL::SSL)
 target_include_directories(rbd_internal PRIVATE ${OPENSSL_INCLUDE_DIR})
+if(LINUX AND HAVE_LIBCRYPTSETUP)
+  target_include_directories(rbd_internal PRIVATE ${LIBCRYPTSETUP_INCLUDE_DIR})
+  target_link_libraries(rbd_internal PRIVATE ${LIBCRYPTSETUP_LIBRARIES})
+endif()
 
 if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
   target_link_libraries(rbd_internal
index a8203e13647621fc427cb2045902f4762cf6eeb7..f37e78f245bd857cce0451045276f01a6b25bf04 100644 (file)
@@ -12,9 +12,9 @@ namespace crypto {
 
 template <typename T>
 BlockCrypto<T>::BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
-                            uint64_t block_size)
+                            uint64_t block_size, uint64_t data_offset)
      : m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_size),
-       m_iv_size(data_cryptor->get_iv_size()) {
+       m_data_offset(data_offset), m_iv_size(data_cryptor->get_iv_size()) {
   ceph_assert(isp2(block_size));
   ceph_assert((block_size % data_cryptor->get_block_size()) == 0);
 }
@@ -100,3 +100,5 @@ int BlockCrypto<T>::decrypt(ceph::bufferlist* data, uint64_t image_offset) {
 
 } // namespace crypto
 } // namespace librbd
+
+template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;
index 8dec0c5fd02ae17a5c409c0b5d4cc795172e74cc..a9f0ad3c0029854cf75e52e38b7242e6e2d7f14a 100644 (file)
@@ -6,7 +6,7 @@
 
 #include "include/Context.h"
 #include "librbd/crypto/CryptoInterface.h"
-#include "librbd/crypto/DataCryptor.h"
+#include "librbd/crypto/openssl/DataCryptor.h"
 
 namespace librbd {
 namespace crypto {
@@ -15,8 +15,12 @@ template <typename T>
 class BlockCrypto : public CryptoInterface {
 
 public:
+    static BlockCrypto* create(CephContext* cct, DataCryptor<T>* data_cryptor,
+                               uint32_t block_size, uint64_t data_offset) {
+      return new BlockCrypto(cct, data_cryptor, block_size, data_offset);
+    }
     BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
-                uint64_t block_size);
+                uint64_t block_size, uint64_t data_offset);
 
     int encrypt(ceph::bufferlist* data, uint64_t image_offset) override;
     int decrypt(ceph::bufferlist* data, uint64_t image_offset) override;
@@ -25,10 +29,15 @@ public:
       return m_block_size;
     }
 
+    uint64_t get_data_offset() const override {
+      return m_data_offset;
+    }
+
 private:
     CephContext* m_cct;
     DataCryptor<T>* m_data_cryptor;
     uint64_t m_block_size;
+    uint64_t m_data_offset;
     uint32_t m_iv_size;
 
     int crypt(ceph::bufferlist* data, uint64_t image_offset, CipherMode mode);
@@ -37,4 +46,6 @@ private:
 } // namespace crypto
 } // namespace librbd
 
+extern template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;
+
 #endif //CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
index cf60874aa315a8f9d60e5a8cdd150718d6741b9c..29e205188fe8928ce80deb38d9fa12b1cbd305fa 100644 (file)
@@ -18,6 +18,7 @@ public:
   virtual int encrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
   virtual int decrypt(ceph::bufferlist* data, uint64_t image_offset) = 0;
   virtual uint64_t get_block_size() const = 0;
+  virtual uint64_t get_data_offset() const = 0;
 
   inline std::pair<uint64_t, uint64_t> get_pre_and_post_align(
           uint64_t off, uint64_t len) {
index 93d9c172c06223b34965054410433fbb13dd376f..c7b29d687dd716ec06b3f4b37637b263958173f4 100644 (file)
@@ -12,6 +12,16 @@ enum CipherMode {
     CIPHER_MODE_DEC,
 };
 
+enum DiskEncryptionFormat {
+    DISK_ENCRYPTION_FORMAT_LUKS1,
+    DISK_ENCRYPTION_FORMAT_LUKS2,
+};
+
+enum CipherAlgorithm {
+    CIPHER_ALGORITHM_AES128,
+    CIPHER_ALGORITHM_AES256,
+};
+
 } // namespace crypto
 } // namespace librbd
 
diff --git a/src/librbd/crypto/luks/FormatRequest.cc b/src/librbd/crypto/luks/FormatRequest.cc
new file mode 100644 (file)
index 0000000..74e6002
--- /dev/null
@@ -0,0 +1,147 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "FormatRequest.h"
+
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/luks/Header.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::luks::FormatRequest: " << this \
+                           << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+FormatRequest<I>::FormatRequest(
+        I* image_ctx, DiskEncryptionFormat type, CipherAlgorithm cipher,
+        std::string&& passphrase, Context* on_finish,
+        bool insecure_fast_mode) : m_image_ctx(image_ctx), m_type(type),
+                                   m_cipher(cipher), m_on_finish(on_finish),
+                                   m_insecure_fast_mode(insecure_fast_mode),
+                                   m_header(image_ctx->cct),
+                                   m_passphrase(std::move(passphrase)) {
+}
+
+template <typename I>
+void FormatRequest<I>::send() {
+  if (m_image_ctx->io_object_dispatcher->exists(
+          io::OBJECT_DISPATCH_LAYER_CRYPTO)) {
+    finish(-EEXIST);
+    return;
+  }
+
+  const char* type;
+  size_t sector_size;
+  switch(m_type) {
+    case DISK_ENCRYPTION_FORMAT_LUKS1:
+      type = CRYPT_LUKS1;
+      sector_size = 512;
+      break;
+    case DISK_ENCRYPTION_FORMAT_LUKS2:
+      type = CRYPT_LUKS2;
+      sector_size = 4096;
+      break;
+    default:
+      lderr(m_image_ctx->cct) << "unsupported disk encryption type: " << m_type
+                              << dendl;
+      finish(-EINVAL);
+      return;
+  }
+
+  const char* alg;
+  size_t key_size;
+  switch (m_cipher) {
+    case CIPHER_ALGORITHM_AES128:
+      alg = "aes";
+      key_size = 32;
+      break;
+    case CIPHER_ALGORITHM_AES256:
+      alg = "aes";
+      key_size = 64;
+      break;
+    default:
+      lderr(m_image_ctx->cct) << "unsupported cipher algorithm: " << m_cipher
+                              << dendl;
+      finish(-EINVAL);
+      return;
+  }
+
+  // setup interface with libcryptsetup
+  auto r = m_header.init();
+  if (r < 0) {
+    finish(r);
+    return;
+  }
+
+  // format (create LUKS header)
+  r = m_header.format(type, alg, key_size, "xts-plain64", sector_size,
+                      m_image_ctx->get_object_size(), m_insecure_fast_mode);
+  if (r != 0) {
+    finish(r);
+    return;
+  }
+
+  // add keyslot (volume key encrypted with passphrase)
+  r = m_header.add_keyslot(m_passphrase.c_str(), m_passphrase.size());
+  if (r != 0) {
+    finish(r);
+    return;
+  }
+
+  // read header from libcryptsetup interface
+  ceph::bufferlist bl;
+  r = m_header.read(&bl);
+  if (r < 0) {
+    finish(r);
+    return;
+  }
+
+  // write header to offset 0 of the image
+  auto ctx = create_context_callback<
+          FormatRequest<I>, &FormatRequest<I>::handle_write_header>(this);
+  auto aio_comp = io::AioCompletion::create_and_start(
+          ctx, util::get_image_ctx(m_image_ctx), io::AIO_TYPE_WRITE);
+
+  ZTracer::Trace trace;
+  auto req = io::ImageDispatchSpec::create_write(
+          *m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
+          {{0, bl.length()}}, std::move(bl),
+          m_image_ctx->get_data_io_context(), 0, trace);
+  req->send();
+}
+
+template <typename I>
+void FormatRequest<I>::handle_write_header(int r) {
+  if (r < 0) {
+    lderr(m_image_ctx->cct) << "error writing header to image: "
+                            << cpp_strerror(r) << dendl;
+    finish(r);
+    return;
+  }
+
+  finish(0);
+}
+
+template <typename I>
+void FormatRequest<I>::finish(int r) {
+  explicit_bzero(&m_passphrase[0], m_passphrase.size());
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/FormatRequest.h b/src/librbd/crypto/luks/FormatRequest.h
new file mode 100644 (file)
index 0000000..3574ed3
--- /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
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
+
+#include "librbd/ImageCtx.h"
+#include "librbd/crypto/Types.h"
+#include "librbd/crypto/luks/Header.h"
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace crypto {
+namespace luks {
+
+template <typename I>
+class FormatRequest {
+public:
+    static FormatRequest* create(
+            I* image_ctx, DiskEncryptionFormat type, CipherAlgorithm cipher,
+            std::string&& passphrase, Context* on_finish,
+            bool insecure_fast_mode) {
+      return new FormatRequest(image_ctx, type, cipher, std::move(passphrase),
+                               on_finish, insecure_fast_mode);
+    }
+
+    FormatRequest(I* image_ctx, DiskEncryptionFormat type,
+                  CipherAlgorithm cipher, std::string&& passphrase,
+                  Context* on_finish, bool insecure_fast_mode);
+    void send();
+    void finish(int r);
+
+private:
+    I* m_image_ctx;
+
+    DiskEncryptionFormat m_type;
+    CipherAlgorithm m_cipher;
+    Context* m_on_finish;
+    bool m_insecure_fast_mode;
+    Header m_header;
+    std::string m_passphrase;
+
+    void handle_write_header(int r);
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
diff --git a/src/librbd/crypto/luks/Header.cc b/src/librbd/crypto/luks/Header.cc
new file mode 100644 (file)
index 0000000..3dbc7d3
--- /dev/null
@@ -0,0 +1,248 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "Header.h"
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include "common/dout.h"
+#include "common/errno.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::luks::Header: " << this << " " \
+                           << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+Header::Header(CephContext* cct) : m_cct(cct), m_fd(-1), m_cd(nullptr) {
+}
+
+Header::~Header() {
+  if (m_fd != -1) {
+    close(m_fd);
+    m_fd = -1;
+  }
+  if (m_cd != nullptr) {
+    crypt_free(m_cd);
+    m_cd = nullptr;
+  }
+}
+
+void Header::libcryptsetup_log_wrapper(int level, const char* msg, void* header) {
+  ((Header*)header)->libcryptsetup_log(level, msg);
+}
+
+void Header::libcryptsetup_log(int level, const char* msg) {
+  switch (level) {
+    case CRYPT_LOG_NORMAL:
+      ldout(m_cct, 5) << "[libcryptsetup] " << msg << dendl;
+      break;
+    case CRYPT_LOG_ERROR:
+      lderr(m_cct) << "[libcryptsetup] " << msg << dendl;
+      break;
+    case CRYPT_LOG_VERBOSE:
+      ldout(m_cct, 10) << "[libcryptsetup] " << msg << dendl;
+      break;
+    case CRYPT_LOG_DEBUG:
+      ldout(m_cct, 20) << "[libcryptsetup] " << msg << dendl;
+      break;
+  }
+}
+
+int Header::init() {
+  // create anonymous file
+  m_fd = syscall(SYS_memfd_create, "LibcryptsetupInterface", 0);
+  if (m_fd == -1) {
+    lderr(m_cct) << "error creating anonymous file: " << cpp_strerror(-errno)
+                 << dendl;
+    return -errno;
+  }
+  std::string path =
+          "/proc/" + std::to_string(getpid()) + "/fd/" + std::to_string(m_fd);
+
+  if (m_cct->_conf->subsys.should_gather<dout_subsys, 20>()) {
+    crypt_set_debug_level(CRYPT_DEBUG_ALL);
+  }
+
+  // init libcryptsetup handle
+  auto r = crypt_init(&m_cd, path.c_str());
+  if (r != 0) {
+    lderr(m_cct) << "crypt_init failed: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  // redirect logging
+  crypt_set_log_callback(m_cd, &libcryptsetup_log_wrapper, this);
+
+  return 0;
+}
+
+int Header::write(const ceph::bufferlist& bl) {
+  ceph_assert(m_fd != -1);
+
+  auto r = bl.write_fd(m_fd);
+  if (r != 0) {
+    lderr(m_cct) << "error writing header: " << cpp_strerror(r) << dendl;
+  }
+  return r;
+}
+
+ssize_t Header::read(ceph::bufferlist* bl) {
+  ceph_assert(m_fd != -1);
+
+  // get current header size
+  struct stat st;
+  ssize_t r = fstat(m_fd, &st);
+  if (r < 0) {
+    r = -errno;
+    lderr(m_cct) << "failed to stat anonymous file: " << cpp_strerror(r)
+                 << dendl;
+    return r;
+  }
+
+  r = bl->read_fd(m_fd, st.st_size);
+  if (r < 0) {
+    lderr(m_cct) << "error reading header: " << cpp_strerror(r) << dendl;
+  }
+  return r;
+}
+
+int Header::format(const char* type, const char* alg, size_t key_size,
+                   const char* cipher_mode, uint32_t sector_size,
+                   uint32_t data_alignment, bool insecure_fast_mode) {
+  ceph_assert(m_cd != nullptr);
+
+  // required for passing libcryptsetup device size check
+  if (ftruncate(m_fd, 4096) != 0) {
+    lderr(m_cct) << "failed to truncate anonymous file: "
+                 << cpp_strerror(-errno) << dendl;
+    return -errno;
+  }
+
+  struct crypt_params_luks1 luks1params;
+  struct crypt_params_luks2 luks2params;
+
+#ifdef LIBCRYPTSETUP_LEGACY_DATA_ALIGNMENT
+  size_t converted_data_alignment = data_alignment / sector_size;
+#else
+  size_t converted_data_alignment = data_alignment / 512;
+#endif
+
+
+  void* params = nullptr;
+  if (strcmp(type, CRYPT_LUKS1) == 0) {
+    memset(&luks1params, 0, sizeof(luks1params));
+    luks1params.data_alignment = converted_data_alignment;
+    params = &luks1params;
+  } else if (strcmp(type, CRYPT_LUKS2) == 0) {
+    memset(&luks2params, 0, sizeof(luks2params));
+    luks2params.data_alignment = converted_data_alignment;
+    luks2params.sector_size = sector_size;
+    params = &luks2params;
+  }
+
+  // this mode should be used for testing only
+  if (insecure_fast_mode) {
+    struct crypt_pbkdf_type pbkdf;
+    memset(&pbkdf, 0, sizeof(pbkdf));
+    pbkdf.type = CRYPT_KDF_PBKDF2;
+    pbkdf.flags = CRYPT_PBKDF_NO_BENCHMARK;
+    pbkdf.hash = "sha256";
+    pbkdf.iterations = 1000;
+    pbkdf.time_ms = 1;
+    auto r = crypt_set_pbkdf_type(m_cd, &pbkdf);
+    if (r != 0) {
+      lderr(m_cct) << "crypt_set_pbkdf_type failed: " << cpp_strerror(r)
+                   << dendl;
+      return r;
+    }
+  }
+
+  auto r = crypt_format(
+          m_cd, type, alg, cipher_mode, NULL, NULL, key_size, params);
+  if (r != 0) {
+    lderr(m_cct) << "crypt_format failed: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
+int Header::add_keyslot(const char* passphrase, size_t passphrase_size) {
+  ceph_assert(m_cd != nullptr);
+
+  auto r = crypt_keyslot_add_by_volume_key(
+          m_cd, CRYPT_ANY_SLOT, NULL, 0, passphrase, passphrase_size);
+  if (r != 0) {
+    lderr(m_cct) << "crypt_keyslot_add_by_volume_key failed: "
+                 << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
+int Header::load() {
+  ceph_assert(m_cd != nullptr);
+
+  // libcryptsetup checks if device size matches the header and keyslots size
+  // in LUKS2, 2 X 4MB header + 128MB keyslots
+  if (ftruncate(m_fd, 136 * 1024 * 1024) != 0) {
+    lderr(m_cct) << "failed to truncate anonymous file: "
+                 << cpp_strerror(-errno) << dendl;
+    return -errno;
+  }
+
+  auto r = crypt_load(m_cd, CRYPT_LUKS, NULL);
+  if (r != 0) {
+    lderr(m_cct) << "crypt_load failed: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
+int Header::read_volume_key(const char* passphrase, size_t passphrase_size,
+                            char* volume_key, size_t* volume_key_size) {
+  ceph_assert(m_cd != nullptr);
+
+  auto r = crypt_volume_key_get(
+          m_cd, CRYPT_ANY_SLOT, volume_key, volume_key_size, passphrase,
+          passphrase_size);
+  if (r != 0) {
+    lderr(m_cct) << "crypt_volume_key_get failed: " << cpp_strerror(r)
+                 << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
+int Header::get_sector_size() {
+  ceph_assert(m_cd != nullptr);
+  return crypt_get_sector_size(m_cd);
+}
+
+uint64_t Header::get_data_offset() {
+  ceph_assert(m_cd != nullptr);
+  return crypt_get_data_offset(m_cd) << 9;
+}
+
+const char* Header::get_cipher() {
+  ceph_assert(m_cd != nullptr);
+  return crypt_get_cipher(m_cd);
+}
+
+const char* Header::get_cipher_mode() {
+  ceph_assert(m_cd != nullptr);
+  return crypt_get_cipher_mode(m_cd);
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/luks/Header.h b/src/librbd/crypto/luks/Header.h
new file mode 100644 (file)
index 0000000..6b58f24
--- /dev/null
@@ -0,0 +1,51 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
+
+#include <libcryptsetup.h>
+#include "common/ceph_context.h"
+#include "include/buffer.h"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+class Header {
+public:
+    Header(CephContext* cct);
+    ~Header();
+    int init();
+
+    int write(const ceph::bufferlist& bl);
+    ssize_t read(ceph::bufferlist* bl);
+
+    int format(const char* type, const char* alg, size_t key_size,
+               const char* cipher_mode, uint32_t sector_size,
+               uint32_t data_alignment, bool insecure_fast_mode);
+    int add_keyslot(const char* passphrase, size_t passphrase_size);
+    int load();
+    int read_volume_key(const char* passphrase, size_t passphrase_size,
+                        char* volume_key, size_t* volume_key_size);
+
+    int get_sector_size();
+    uint64_t get_data_offset();
+    const char* get_cipher();
+    const char* get_cipher_mode();
+
+private:
+    void libcryptsetup_log(int level, const char* msg);
+    static void libcryptsetup_log_wrapper(int level, const char* msg,
+                                          void* header);
+
+    CephContext* m_cct;
+    int m_fd;
+    struct crypt_device *m_cd;
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
diff --git a/src/librbd/crypto/luks/LoadRequest.cc b/src/librbd/crypto/luks/LoadRequest.cc
new file mode 100644 (file)
index 0000000..5229b6e
--- /dev/null
@@ -0,0 +1,214 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "LoadRequest.h"
+
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/BlockCrypto.h"
+#include "librbd/crypto/openssl/DataCryptor.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+#include "librbd/io/ReadResult.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::luks::LoadRequest: " << this \
+                           << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+LoadRequest<I>::LoadRequest(
+        I* image_ctx, std::string&& passphrase,
+        ceph::ref_t<CryptoInterface>* result_crypto,
+        Context* on_finish) : m_image_ctx(image_ctx), m_on_finish(on_finish),
+                              m_result_crypto(result_crypto),
+                              m_initial_read_size(DEFAULT_INITIAL_READ_SIZE),
+                              m_header(image_ctx->cct), m_offset(0),
+                              m_passphrase(std::move(passphrase)) {
+}
+
+template <typename I>
+void LoadRequest<I>::set_initial_read_size(uint64_t read_size) {
+  m_initial_read_size = read_size;
+}
+
+template <typename I>
+void LoadRequest<I>::send() {
+  if (m_image_ctx->io_object_dispatcher->exists(
+          io::OBJECT_DISPATCH_LAYER_CRYPTO)) {
+    finish(-EEXIST);
+    return;
+  }
+
+  // setup interface with libcryptsetup
+  auto r = m_header.init();
+  if (r < 0) {
+    finish(r);
+    return;
+  }
+
+  auto ctx = create_context_callback<
+          LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
+  read(m_initial_read_size, ctx);
+}
+
+template <typename I>
+void LoadRequest<I>::read(uint64_t end_offset, Context* on_finish) {
+  auto length = end_offset - m_offset;
+  auto aio_comp = io::AioCompletion::create_and_start(
+          on_finish, util::get_image_ctx(m_image_ctx), io::AIO_TYPE_READ);
+  ZTracer::Trace trace;
+  auto req = io::ImageDispatchSpec::create_read(
+          *m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
+          {{m_offset, length}}, io::ReadResult{&m_bl},
+          m_image_ctx->get_data_io_context(), 0, 0, trace);
+  req->send();
+}
+
+template <typename I>
+bool LoadRequest<I>::handle_read(int r) {
+  if (r < 0) {
+    lderr(m_image_ctx->cct) << "error reading from image: " << cpp_strerror(r)
+                            << dendl;
+    finish(r);
+    return false;
+  }
+
+  // write header to libcryptsetup interface
+  r = m_header.write(m_bl);
+  if (r < 0) {
+    finish(r);
+    return false;
+  }
+
+  m_offset += m_bl.length();
+  m_bl.clear();
+  return true;
+}
+
+template <typename I>
+void LoadRequest<I>::handle_read_header(int r) {
+  if (!handle_read(r)) {
+    return;
+  }
+
+  // parse header via libcryptsetup
+  r = m_header.load();
+  if (r != 0) {
+    if (m_offset < MAXIMUM_HEADER_SIZE) {
+      // perhaps we did not feed the entire header to libcryptsetup, retry
+      auto ctx = create_context_callback<
+              LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
+      read(MAXIMUM_HEADER_SIZE, ctx);
+      return;
+    }
+
+    finish(r);
+    return;
+  }
+
+  auto cipher = m_header.get_cipher();
+  if (strcmp(cipher, "aes") != 0) {
+    lderr(m_image_ctx->cct) << "unsupported cipher: " << cipher << dendl;
+    finish(-ENOTSUP);
+    return;
+  }
+
+  auto cipher_mode = m_header.get_cipher_mode();
+  if (strcmp(cipher_mode, "xts-plain64") != 0) {
+    lderr(m_image_ctx->cct) << "unsupported cipher mode: " << cipher_mode
+                            << dendl;
+    finish(-ENOTSUP);
+    return;
+  }
+
+  read_volume_key();
+  return;
+}
+
+template <typename I>
+void LoadRequest<I>::handle_read_keyslots(int r) {
+  if (!handle_read(r)) {
+    return;
+  }
+
+  read_volume_key();
+}
+
+template <typename I>
+void LoadRequest<I>::read_volume_key() {
+  char volume_key[64];
+  size_t volume_key_size = sizeof(volume_key);
+
+  auto r = m_header.read_volume_key(
+          m_passphrase.c_str(), m_passphrase.size(),
+          reinterpret_cast<char*>(volume_key), &volume_key_size);
+  if (r != 0) {
+    auto keyslots_end_offset = m_header.get_data_offset();
+    if (m_offset < keyslots_end_offset) {
+      // perhaps we did not feed the the necessary keyslot, retry
+      auto ctx = create_context_callback<
+              LoadRequest<I>, &LoadRequest<I>::handle_read_keyslots>(this);
+      read(keyslots_end_offset, ctx);
+      return;
+    }
+
+    finish(r);
+    return;
+  }
+
+  const char* cipher_suite;
+  switch (volume_key_size) {
+    case 32:
+      cipher_suite = "aes-128-xts";
+      break;
+    case 64:
+      cipher_suite = "aes-256-xts";
+      break;
+    default:
+      lderr(m_image_ctx->cct) << "unsupported volume key size: "
+                              << volume_key_size << dendl;
+      finish(-ENOTSUP);
+      return;
+  }
+
+
+  auto data_cryptor = new openssl::DataCryptor(m_image_ctx->cct);
+  r = data_cryptor->init(
+          cipher_suite, reinterpret_cast<unsigned char*>(volume_key),
+          volume_key_size);
+  if (r != 0) {
+    lderr(m_image_ctx->cct) << "error initializing data cryptor: " << r
+                            << dendl;
+    delete data_cryptor;
+    finish(r);
+    return;
+  }
+
+  auto sector_size = m_header.get_sector_size();
+  auto data_offset = m_header.get_data_offset();
+  *m_result_crypto = BlockCrypto<EVP_CIPHER_CTX>::create(
+          m_image_ctx->cct, data_cryptor, sector_size, data_offset);
+  finish(0);
+}
+
+template <typename I>
+void LoadRequest<I>::finish(int r) {
+  explicit_bzero(&m_passphrase[0], m_passphrase.size());
+  m_on_finish->complete(r);
+  delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/LoadRequest.h b/src/librbd/crypto/luks/LoadRequest.h
new file mode 100644 (file)
index 0000000..4c7dca7
--- /dev/null
@@ -0,0 +1,63 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H
+
+#include "librbd/ImageCtx.h"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/crypto/luks/Header.h"
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace crypto {
+namespace luks {
+
+// max header size in LUKS1/2 (excl. keyslots) is 4MB
+const uint64_t MAXIMUM_HEADER_SIZE = 4 * 1024 * 1024;
+// default header size in LUKS2 2 X 16KB + 1 X 256KB keyslot
+const uint64_t DEFAULT_INITIAL_READ_SIZE = 288 * 1024;
+
+template <typename I>
+class LoadRequest {
+public:
+    static LoadRequest* create(
+            I* image_ctx, std::string&& passphrase,
+            ceph::ref_t<CryptoInterface>* result_crypto, Context* on_finish) {
+      return new LoadRequest(image_ctx, std::move(passphrase), result_crypto,
+                             on_finish);
+    }
+
+    LoadRequest(I* image_ctx, std::string&& passphrase,
+                ceph::ref_t<CryptoInterface>* result_crypto,
+                Context* on_finish);
+    void send();
+    void finish(int r);
+    void set_initial_read_size(uint64_t read_size);
+
+private:
+    I* m_image_ctx;
+    Context* m_on_finish;
+    ceph::bufferlist m_bl;
+    ceph::ref_t<CryptoInterface>* m_result_crypto;
+    uint64_t m_initial_read_size;
+    Header m_header;
+    uint64_t m_offset;
+    std::string m_passphrase;
+
+    void read(uint64_t end_offset, Context* on_finish);
+    bool handle_read(int r);
+    void handle_read_header(int r);
+    void handle_read_keyslots(int r);
+    void read_volume_key();
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H
index 7dc9357bac971107670c964a85c2a48950dd90f9..cb64e11b27efa528e7774590f37935ad4a9a6ea3 100644 (file)
@@ -68,6 +68,11 @@ public:
     ceph_assert(result.second);
   }
 
+  bool exists(DispatchLayer dispatch_layer) override {
+    std::unique_lock locker{m_lock};
+    return m_dispatches.find(dispatch_layer) != m_dispatches.end();
+  }
+
   void shut_down_dispatch(DispatchLayer dispatch_layer,
                           Context* on_finish) override {
     auto cct = m_image_ctx->cct;
index 57a56602dfd06b977f0f91a3900dd49c156b7876..2bac9ee757a45a60aa233767844eb7408921f58c 100644 (file)
@@ -24,6 +24,7 @@ public:
   virtual void shut_down(Context* on_finish) = 0;
 
   virtual void register_dispatch(Dispatch* dispatch) = 0;
+  virtual bool exists(DispatchLayer dispatch_layer) = 0;
   virtual void shut_down_dispatch(DispatchLayer dispatch_layer,
                                   Context* on_finish) = 0;
 
index 12d80ccecba30fcaf8fa2df4108d71103c672f14..fda6ecf00cb535edf8aea75186e015ebcdcc18e8 100644 (file)
@@ -132,6 +132,12 @@ if(WITH_RBD_RWL)
      cache/pwl/test_WriteLogMap.cc)
 endif(WITH_RBD_RWL)
 
+if(LINUX AND HAVE_LIBCRYPTSETUP)
+  list(APPEND unittest_librbd_srcs
+          crypto/luks/test_mock_FormatRequest.cc
+          crypto/luks/test_mock_LoadRequest.cc)
+endif()
+
 add_executable(unittest_librbd
   ${unittest_librbd_srcs}
   $<TARGET_OBJECTS:common_texttable_obj>)
diff --git a/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
new file mode 100644 (file)
index 0000000..36f99fe
--- /dev/null
@@ -0,0 +1,181 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+namespace librbd {
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+  return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/luks/FormatRequest.cc"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockCryptoLuksFormatRequest : public TestMockFixture {
+  typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest;
+
+  const size_t OBJECT_SIZE = 4 * 1024 * 1024;
+  const char* passphrase_cstr = "password";
+  std::string passphrase = passphrase_cstr;
+
+  MockImageCtx* mock_image_ctx;
+  C_SaferCond finished_cond;
+  Context *on_finish = &finished_cond;
+  io::AioCompletion* aio_comp;
+  ceph::bufferlist header_bl;
+
+  void SetUp() override {
+    TestMockFixture::SetUp();
+
+    librbd::ImageCtx *ictx;
+    ASSERT_EQ(0, open_image(m_image_name, &ictx));
+    mock_image_ctx = new MockImageCtx(*ictx);
+  }
+
+  void TearDown() override {
+    delete mock_image_ctx;
+    TestMockFixture::TearDown();
+  }
+
+  void expect_get_object_size() {
+    EXPECT_CALL(*mock_image_ctx, get_object_size()).WillOnce(Return(
+            OBJECT_SIZE));
+  }
+
+  void expect_crypto_layer_exists_check(bool exists = false) {
+    EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists(
+            io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+  }
+
+  void expect_image_write() {
+    EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
+            .WillOnce(Invoke([this](io::ImageDispatchSpec* spec) {
+                auto* write = boost::get<io::ImageDispatchSpec::Write>(
+                        &spec->request);
+                ASSERT_TRUE(write != nullptr);
+
+                ASSERT_EQ(1, spec->image_extents.size());
+                ASSERT_EQ(0, spec->image_extents[0].first);
+                ASSERT_GT(spec->image_extents[0].second, 0);
+
+                spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+                aio_comp = spec->aio_comp;
+                header_bl = write->bl;
+            }));
+  }
+
+  void complete_aio(int r) {
+    if (r < 0) {
+      aio_comp->fail(r);
+    } else {
+      aio_comp->set_request_count(1);
+      aio_comp->add_request();
+      aio_comp->complete_request(r);
+    }
+  }
+
+  void verify_header(size_t expected_key_length,
+                     uint64_t expected_sector_size) {
+    Header header(mock_image_ctx->cct);
+
+    ASSERT_EQ(0, header.init());
+    ASSERT_EQ(0, header.write(header_bl));
+    ASSERT_EQ(0, header.load());
+
+    ASSERT_EQ(expected_sector_size, header.get_sector_size());
+    ASSERT_EQ(0, header.get_data_offset() % OBJECT_SIZE);
+
+    char volume_key[64];
+    size_t volume_key_size = sizeof(volume_key);
+    ASSERT_EQ(0, header.read_volume_key(
+            passphrase_cstr, strlen(passphrase_cstr),
+            reinterpret_cast<char*>(volume_key), &volume_key_size));
+  }
+};
+
+TEST_F(TestMockCryptoLuksFormatRequest, LUKS1) {
+  auto mock_format_request = MockFormatRequest::create(
+          mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS1,
+          CipherAlgorithm::CIPHER_ALGORITHM_AES128, std::move(passphrase),
+          on_finish, true);
+  expect_crypto_layer_exists_check();
+  expect_get_object_size();
+  expect_image_write();
+  mock_format_request->send();
+  ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+  complete_aio(0);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NO_FATAL_FAILURE(verify_header(32, 512));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, AES128) {
+  auto mock_format_request = MockFormatRequest::create(
+          mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
+          CipherAlgorithm::CIPHER_ALGORITHM_AES128, std::move(passphrase),
+          on_finish, true);
+  expect_crypto_layer_exists_check();
+  expect_get_object_size();
+  expect_image_write();
+  mock_format_request->send();
+  ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+  complete_aio(0);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NO_FATAL_FAILURE(verify_header(32, 4096));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, AES256) {
+  auto mock_format_request = MockFormatRequest::create(
+          mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
+          CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
+          on_finish, true);
+  expect_crypto_layer_exists_check();
+  expect_get_object_size();
+  expect_image_write();
+  mock_format_request->send();
+  ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+  complete_aio(0);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NO_FATAL_FAILURE(verify_header(62, 4096));
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, CryptoAlreadyLoaded) {
+  auto mock_format_request = MockFormatRequest::create(
+          mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
+          CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
+          on_finish, true);
+  expect_crypto_layer_exists_check(true);
+  mock_format_request->send();
+  ASSERT_EQ(-EEXIST, finished_cond.wait());
+}
+
+TEST_F(TestMockCryptoLuksFormatRequest, WriteFail) {
+  auto mock_format_request = MockFormatRequest::create(
+          mock_image_ctx, DiskEncryptionFormat::DISK_ENCRYPTION_FORMAT_LUKS2,
+          CipherAlgorithm::CIPHER_ALGORITHM_AES256, std::move(passphrase),
+          on_finish, true);
+  expect_crypto_layer_exists_check();
+  expect_get_object_size();
+  expect_image_write();
+  mock_format_request->send();
+  ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0));
+  complete_aio(-123);
+  ASSERT_EQ(-123, finished_cond.wait());
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
new file mode 100644 (file)
index 0000000..5511fbe
--- /dev/null
@@ -0,0 +1,220 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "test/librbd/mock/MockImageCtx.h"
+
+namespace librbd {
+namespace util {
+
+inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) {
+  return image_ctx->image_ctx;
+}
+
+} // namespace util
+} // namespace librbd
+
+#include "librbd/crypto/luks/LoadRequest.cc"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+
+struct TestMockCryptoLuksLoadRequest : public TestMockFixture {
+  typedef LoadRequest<librbd::MockImageCtx> MockLoadRequest;
+
+  const size_t OBJECT_SIZE = 4 * 1024 * 1024;
+  const char* passphrase_cstr = "password";
+  std::string passphrase = passphrase_cstr;
+
+  MockImageCtx* mock_image_ctx;
+  ceph::ref_t<CryptoInterface> crypto;
+  MockLoadRequest* mock_load_request;
+  C_SaferCond finished_cond;
+  Context *on_finish = &finished_cond;
+  Context* image_read_request;
+  ceph::bufferlist header_bl;
+  uint64_t data_offset;
+
+  void SetUp() override {
+    TestMockFixture::SetUp();
+
+    librbd::ImageCtx *ictx;
+    ASSERT_EQ(0, open_image(m_image_name, &ictx));
+    mock_image_ctx = new MockImageCtx(*ictx);
+    crypto = nullptr;
+    mock_load_request = MockLoadRequest::create(
+            mock_image_ctx, std::move(passphrase), &crypto, on_finish);
+  }
+
+  void TearDown() override {
+    delete mock_image_ctx;
+    if (crypto != nullptr) {
+      crypto = nullptr;
+    }
+    TestMockFixture::TearDown();
+  }
+
+  // returns data offset in bytes
+  void generate_header(const char* type, const char* alg, size_t key_size,
+                       const char* cipher_mode, uint32_t sector_size) {
+    Header header(mock_image_ctx->cct);
+
+    ASSERT_EQ(0, header.init());
+    ASSERT_EQ(0, header.format(type, alg, key_size, cipher_mode, sector_size,
+                               OBJECT_SIZE, true));
+    ASSERT_EQ(0, header.add_keyslot(passphrase_cstr, strlen(passphrase_cstr)));
+    ASSERT_LE(0, header.read(&header_bl));
+
+    data_offset = header.get_data_offset();
+  }
+
+    void expect_crypto_layer_exists_check(bool exists = false) {
+      EXPECT_CALL(*mock_image_ctx->io_object_dispatcher, exists(
+              io::OBJECT_DISPATCH_LAYER_CRYPTO)).WillOnce(Return(exists));
+    }
+
+    void expect_image_read(uint64_t offset, uint64_t length) {
+    EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_))
+            .WillOnce(Invoke([this, offset,
+                              length](io::ImageDispatchSpec* spec) {
+                auto* read = boost::get<io::ImageDispatchSpec::Read>(
+                        &spec->request);
+                ASSERT_TRUE(read != nullptr);
+
+                ASSERT_EQ(1, spec->image_extents.size());
+                ASSERT_EQ(offset, spec->image_extents[0].first);
+                ASSERT_EQ(length, spec->image_extents[0].second);
+
+                spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+                auto aio_comp = spec->aio_comp;
+                aio_comp->set_request_count(1);
+                aio_comp->read_result = std::move(read->read_result);
+                aio_comp->read_result.set_image_extents(spec->image_extents);
+                auto ctx = new io::ReadResult::C_ImageReadRequest(
+                        aio_comp, 0, spec->image_extents);
+                if (header_bl.length() < offset + length) {
+                  header_bl.append_zero(offset + length - header_bl.length());
+                }
+                ctx->bl.substr_of(header_bl, offset, length);
+                image_read_request = ctx;
+            }));
+  }
+};
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES128) {
+  generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, AES256) {
+  generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) {
+  generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) {
+  generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+  ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) {
+  generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(-ENOTSUP, finished_cond.wait());
+  ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) {
+  generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+  mock_load_request->set_initial_read_size(4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, 4096);
+  mock_load_request->send();
+
+  expect_image_read(4096, MAXIMUM_HEADER_SIZE - 4096);
+  image_read_request->complete(4096); // complete initial read
+
+  image_read_request->complete(MAXIMUM_HEADER_SIZE - 4096);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) {
+  generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+  mock_load_request->set_initial_read_size(16384);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, 16384);
+  mock_load_request->send();
+
+  expect_image_read(16384, data_offset - 16384);
+  image_read_request->complete(16384); // complete initial read
+
+  image_read_request->complete(data_offset - 16384);
+  ASSERT_EQ(0, finished_cond.wait());
+  ASSERT_NE(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) {
+  delete mock_load_request;
+  mock_load_request = MockLoadRequest::create(
+        mock_image_ctx, "wrong", &crypto, on_finish);
+
+  generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096);
+  expect_crypto_layer_exists_check();
+  expect_image_read(0, DEFAULT_INITIAL_READ_SIZE);
+  mock_load_request->send();
+
+  // crypt_volume_key_get will fail, we will retry reading more
+  expect_image_read(DEFAULT_INITIAL_READ_SIZE,
+                    data_offset - DEFAULT_INITIAL_READ_SIZE);
+  image_read_request->complete(DEFAULT_INITIAL_READ_SIZE);
+
+  image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE);
+  ASSERT_EQ(-EPERM, finished_cond.wait());
+  ASSERT_EQ(crypto, nullptr);
+}
+
+TEST_F(TestMockCryptoLuksLoadRequest, CryptoAlreadyLoaded) {
+  generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096);
+  expect_crypto_layer_exists_check(true);
+  mock_load_request->send();
+  ASSERT_EQ(-EEXIST, finished_cond.wait());
+  ASSERT_EQ(crypto, nullptr);
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
index 725101658503a115a97e4a5baa9b739189363628..3f00e239340f195b772ca339b4d5574015879eb1 100644 (file)
@@ -23,10 +23,11 @@ MATCHER_P(CompareArrayToString, s, "") {
 
 struct TestMockBlockCrypto : public TestFixture {
     MockDataCryptor cryptor;
-    BlockCrypto<MockCryptoContext>* bc;
+    ceph::ref_t<BlockCrypto<MockCryptoContext>> bc;
     int cryptor_block_size = 2;
     int cryptor_iv_size = 16;
     int block_size = 4;
+    int data_offset = 0;
     ExpectationSet* expectation_set;
 
     void SetUp() override {
@@ -35,7 +36,7 @@ struct TestMockBlockCrypto : public TestFixture {
       cryptor.block_size = cryptor_block_size;
       bc = new BlockCrypto<MockCryptoContext>(
               reinterpret_cast<CephContext*>(m_ioctx.cct()), &cryptor,
-              block_size);
+              block_size, data_offset);
       expectation_set = new ExpectationSet();
     }
 
index 0745583624ea8a088db151005f41618f8b6e64f8..1263fba95b824437ed1205c02f65b6986c8b404d 100644 (file)
@@ -19,6 +19,10 @@ struct MockCryptoInterface : CryptoInterface {
   uint64_t get_block_size() const override {
     return 4096;
   }
+
+  uint64_t get_data_offset() const override {
+    return 4 * 1024 * 1024;
+  }
 };
 
 } // namespace crypto
index 7d63044cda8102720cb8c2430c1af64a3ee5abb9..bd1c962e8d0c544506c6bb5dacbd8efccf15c208 100644 (file)
@@ -22,6 +22,7 @@ public:
   MOCK_METHOD1(shut_down, void(Context*));
 
   MOCK_METHOD1(register_dispatch, void(ImageDispatchInterface*));
+  MOCK_METHOD1(exists, bool(ImageDispatchLayer));
   MOCK_METHOD2(shut_down_dispatch, void(ImageDispatchLayer, Context*));
   MOCK_METHOD1(invalidate_cache, void(Context *));
 
index 688744bcbe5404264c10c0e1cd5fd16781d5cee8..5e700397bb0a8939a2fab028e520550e7085273e 100644 (file)
@@ -22,6 +22,7 @@ public:
   MOCK_METHOD1(shut_down, void(Context*));
 
   MOCK_METHOD1(register_dispatch, void(ObjectDispatchInterface*));
+  MOCK_METHOD1(exists, bool(ObjectDispatchLayer));
   MOCK_METHOD2(shut_down_dispatch, void(ObjectDispatchLayer, Context*));
 
   MOCK_METHOD2(flush, void(FlushSource, Context*));