mirror of
https://github.com/ceph/ceph
synced 2025-02-23 11:07:35 +00:00
Merge pull request #37875 from orozery/librbd-luks-format
librbd: add LUKS support Reviewed-by: Jason Dillaman <dillaman@redhat.com>
This commit is contained in:
commit
c569a302be
@ -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
|
||||
|
@ -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
|
||||
|
32
cmake/modules/Findlibcryptsetup.cmake
Normal file
32
cmake/modules/Findlibcryptsetup.cmake
Normal file
@ -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)
|
1
debian/control
vendored
1
debian/control
vendored
@ -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,
|
||||
|
@ -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@"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>;
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
147
src/librbd/crypto/luks/FormatRequest.cc
Normal file
147
src/librbd/crypto/luks/FormatRequest.cc
Normal file
@ -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>;
|
54
src/librbd/crypto/luks/FormatRequest.h
Normal file
54
src/librbd/crypto/luks/FormatRequest.h
Normal file
@ -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
|
248
src/librbd/crypto/luks/Header.cc
Normal file
248
src/librbd/crypto/luks/Header.cc
Normal file
@ -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
|
51
src/librbd/crypto/luks/Header.h
Normal file
51
src/librbd/crypto/luks/Header.h
Normal file
@ -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
|
214
src/librbd/crypto/luks/LoadRequest.cc
Normal file
214
src/librbd/crypto/luks/LoadRequest.cc
Normal file
@ -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>;
|
63
src/librbd/crypto/luks/LoadRequest.h
Normal file
63
src/librbd/crypto/luks/LoadRequest.h
Normal file
@ -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
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>)
|
||||
|
181
src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
Normal file
181
src/test/librbd/crypto/luks/test_mock_FormatRequest.cc
Normal file
@ -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
|
220
src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
Normal file
220
src/test/librbd/crypto/luks/test_mock_LoadRequest.cc
Normal file
@ -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
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 *));
|
||||
|
||||
|
@ -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*));
|
||||
|
Loading…
Reference in New Issue
Block a user