From: Or Ozeri Date: Wed, 28 Oct 2020 12:22:06 +0000 (+0200) Subject: librbd: add LUKS support X-Git-Tag: v16.1.0~501^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ea3c1bfb9ef2edcdf572df0cb143c463b7551905;p=ceph.git librbd: add LUKS support 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 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index a5c560fee6d9..5357a2143c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ceph.spec.in b/ceph.spec.in index 81258139d1a0..9ac67112f486 100644 --- a/ceph.spec.in +++ b/ceph.spec.in @@ -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 index 000000000000..98b33383025b --- /dev/null +++ b/cmake/modules/Findlibcryptsetup.cmake @@ -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) diff --git a/debian/control b/debian/control index 13471e67c0ef..1e22cf916433 100644 --- a/debian/control +++ b/debian/control @@ -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, diff --git a/src/include/config-h.in.cmake b/src/include/config-h.in.cmake index f3931951322e..d952983c55b3 100644 --- a/src/include/config-h.in.cmake +++ b/src/include/config-h.in.cmake @@ -360,6 +360,9 @@ /* 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@" diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 6f7977fdae5b..0c36cc79816b 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -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 diff --git a/src/librbd/crypto/BlockCrypto.cc b/src/librbd/crypto/BlockCrypto.cc index a8203e136476..f37e78f245bd 100644 --- a/src/librbd/crypto/BlockCrypto.cc +++ b/src/librbd/crypto/BlockCrypto.cc @@ -12,9 +12,9 @@ namespace crypto { template BlockCrypto::BlockCrypto(CephContext* cct, DataCryptor* 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::decrypt(ceph::bufferlist* data, uint64_t image_offset) { } // namespace crypto } // namespace librbd + +template class librbd::crypto::BlockCrypto; diff --git a/src/librbd/crypto/BlockCrypto.h b/src/librbd/crypto/BlockCrypto.h index 8dec0c5fd02a..a9f0ad3c0029 100644 --- a/src/librbd/crypto/BlockCrypto.h +++ b/src/librbd/crypto/BlockCrypto.h @@ -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 class BlockCrypto : public CryptoInterface { public: + static BlockCrypto* create(CephContext* cct, DataCryptor* data_cryptor, + uint32_t block_size, uint64_t data_offset) { + return new BlockCrypto(cct, data_cryptor, block_size, data_offset); + } BlockCrypto(CephContext* cct, DataCryptor* 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* 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; + #endif //CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H diff --git a/src/librbd/crypto/CryptoInterface.h b/src/librbd/crypto/CryptoInterface.h index cf60874aa315..29e205188fe8 100644 --- a/src/librbd/crypto/CryptoInterface.h +++ b/src/librbd/crypto/CryptoInterface.h @@ -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 get_pre_and_post_align( uint64_t off, uint64_t len) { diff --git a/src/librbd/crypto/Types.h b/src/librbd/crypto/Types.h index 93d9c172c062..c7b29d687dd7 100644 --- a/src/librbd/crypto/Types.h +++ b/src/librbd/crypto/Types.h @@ -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 index 000000000000..74e6002834d3 --- /dev/null +++ b/src/librbd/crypto/luks/FormatRequest.cc @@ -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 +FormatRequest::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 +void FormatRequest::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, &FormatRequest::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 +void FormatRequest::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 +void FormatRequest::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; diff --git a/src/librbd/crypto/luks/FormatRequest.h b/src/librbd/crypto/luks/FormatRequest.h new file mode 100644 index 000000000000..3574ed39aee1 --- /dev/null +++ b/src/librbd/crypto/luks/FormatRequest.h @@ -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 +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; + +#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 index 000000000000..3dbc7d3b0ad1 --- /dev/null +++ b/src/librbd/crypto/luks/Header.cc @@ -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 +#include +#include +#include +#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()) { + 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 index 000000000000..6b58f246423f --- /dev/null +++ b/src/librbd/crypto/luks/Header.h @@ -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 +#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 index 000000000000..5229b6e59030 --- /dev/null +++ b/src/librbd/crypto/luks/LoadRequest.cc @@ -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 +LoadRequest::LoadRequest( + I* image_ctx, std::string&& passphrase, + ceph::ref_t* 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 +void LoadRequest::set_initial_read_size(uint64_t read_size) { + m_initial_read_size = read_size; +} + +template +void LoadRequest::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, &LoadRequest::handle_read_header>(this); + read(m_initial_read_size, ctx); +} + +template +void LoadRequest::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 +bool LoadRequest::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 +void LoadRequest::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, &LoadRequest::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 +void LoadRequest::handle_read_keyslots(int r) { + if (!handle_read(r)) { + return; + } + + read_volume_key(); +} + +template +void LoadRequest::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(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, &LoadRequest::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(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::create( + m_image_ctx->cct, data_cryptor, sector_size, data_offset); + finish(0); +} + +template +void LoadRequest::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; diff --git a/src/librbd/crypto/luks/LoadRequest.h b/src/librbd/crypto/luks/LoadRequest.h new file mode 100644 index 000000000000..4c7dca7bc361 --- /dev/null +++ b/src/librbd/crypto/luks/LoadRequest.h @@ -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 +class LoadRequest { +public: + static LoadRequest* create( + I* image_ctx, std::string&& passphrase, + ceph::ref_t* 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* 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* 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; + +#endif // CEPH_LIBRBD_CRYPTO_LUKS_LOAD_REQUEST_H diff --git a/src/librbd/io/Dispatcher.h b/src/librbd/io/Dispatcher.h index 7dc9357bac97..cb64e11b27ef 100644 --- a/src/librbd/io/Dispatcher.h +++ b/src/librbd/io/Dispatcher.h @@ -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; diff --git a/src/librbd/io/DispatcherInterface.h b/src/librbd/io/DispatcherInterface.h index 57a56602dfd0..2bac9ee757a4 100644 --- a/src/librbd/io/DispatcherInterface.h +++ b/src/librbd/io/DispatcherInterface.h @@ -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; diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 12d80ccecba3..fda6ecf00cb5 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -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} $) 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 index 000000000000..36f99fef6d36 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc @@ -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 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( + &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(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 index 000000000000..5511fbe8f645 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc @@ -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 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 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( + &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 diff --git a/src/test/librbd/crypto/test_mock_BlockCrypto.cc b/src/test/librbd/crypto/test_mock_BlockCrypto.cc index 725101658503..3f00e239340f 100644 --- a/src/test/librbd/crypto/test_mock_BlockCrypto.cc +++ b/src/test/librbd/crypto/test_mock_BlockCrypto.cc @@ -23,10 +23,11 @@ MATCHER_P(CompareArrayToString, s, "") { struct TestMockBlockCrypto : public TestFixture { MockDataCryptor cryptor; - BlockCrypto* bc; + ceph::ref_t> 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( reinterpret_cast(m_ioctx.cct()), &cryptor, - block_size); + block_size, data_offset); expectation_set = new ExpectationSet(); } diff --git a/src/test/librbd/mock/crypto/MockCryptoInterface.h b/src/test/librbd/mock/crypto/MockCryptoInterface.h index 0745583624ea..1263fba95b82 100644 --- a/src/test/librbd/mock/crypto/MockCryptoInterface.h +++ b/src/test/librbd/mock/crypto/MockCryptoInterface.h @@ -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 diff --git a/src/test/librbd/mock/io/MockImageDispatcher.h b/src/test/librbd/mock/io/MockImageDispatcher.h index 7d63044cda81..bd1c962e8d0c 100644 --- a/src/test/librbd/mock/io/MockImageDispatcher.h +++ b/src/test/librbd/mock/io/MockImageDispatcher.h @@ -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 *)); diff --git a/src/test/librbd/mock/io/MockObjectDispatcher.h b/src/test/librbd/mock/io/MockObjectDispatcher.h index 688744bcbe54..5e700397bb0a 100644 --- a/src/test/librbd/mock/io/MockObjectDispatcher.h +++ b/src/test/librbd/mock/io/MockObjectDispatcher.h @@ -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*));