summaryrefslogtreecommitdiffstats
path: root/src/librbd/crypto/luks
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/librbd/crypto/luks/FlattenRequest.cc154
-rw-r--r--src/librbd/crypto/luks/FlattenRequest.h65
-rw-r--r--src/librbd/crypto/luks/FormatRequest.cc200
-rw-r--r--src/librbd/crypto/luks/FormatRequest.h59
-rw-r--r--src/librbd/crypto/luks/Header.cc261
-rw-r--r--src/librbd/crypto/luks/Header.h52
-rw-r--r--src/librbd/crypto/luks/LUKSEncryptionFormat.cc85
-rw-r--r--src/librbd/crypto/luks/LUKSEncryptionFormat.h100
-rw-r--r--src/librbd/crypto/luks/LoadRequest.cc272
-rw-r--r--src/librbd/crypto/luks/LoadRequest.h71
-rw-r--r--src/librbd/crypto/luks/Magic.cc139
-rw-r--r--src/librbd/crypto/luks/Magic.h32
12 files changed, 1490 insertions, 0 deletions
diff --git a/src/librbd/crypto/luks/FlattenRequest.cc b/src/librbd/crypto/luks/FlattenRequest.cc
new file mode 100644
index 000000000..fdf6b5dae
--- /dev/null
+++ b/src/librbd/crypto/luks/FlattenRequest.cc
@@ -0,0 +1,154 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "FlattenRequest.h"
+
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/EncryptionFormat.h"
+#include "librbd/crypto/Utils.h"
+#include "librbd/crypto/luks/LoadRequest.h"
+#include "librbd/crypto/luks/Magic.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ReadResult.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::luks::FlattenRequest: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+FlattenRequest<I>::FlattenRequest(
+ I* image_ctx, Context* on_finish) : m_image_ctx(image_ctx),
+ m_on_finish(on_finish) {
+ ceph_assert(m_image_ctx->encryption_format.get() != nullptr);
+}
+
+template <typename I>
+void FlattenRequest<I>::send() {
+ read_header();
+}
+
+template <typename I>
+void FlattenRequest<I>::read_header() {
+ auto ctx = create_context_callback<
+ FlattenRequest<I>, &FlattenRequest<I>::handle_read_header>(this);
+ auto aio_comp = io::AioCompletion::create_and_start(
+ ctx, librbd::util::get_image_ctx(m_image_ctx), io::AIO_TYPE_READ);
+
+ auto crypto = m_image_ctx->encryption_format->get_crypto();
+ ZTracer::Trace trace;
+ auto req = io::ImageDispatchSpec::create_read(
+ *m_image_ctx, io::IMAGE_DISPATCH_LAYER_API_START, aio_comp,
+ {{0, crypto->get_data_offset()}}, io::ImageArea::CRYPTO_HEADER,
+ io::ReadResult{&m_bl}, m_image_ctx->get_data_io_context(), 0, 0,
+ trace);
+ req->send();
+}
+
+template <typename I>
+void FlattenRequest<I>::handle_read_header(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "error reading from image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ r = Magic::is_rbd_clone(m_bl);
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "unable to determine encryption header magic: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ } else if (r > 0) {
+ // switch magic
+ r = Magic::replace_magic(m_image_ctx->cct, m_bl);
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "unable to restore header magic: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+ }
+
+ write_header();
+}
+
+template <typename I>
+void FlattenRequest<I>::write_header() {
+ // write header to offset 0 of the image
+ auto ctx = create_context_callback<
+ FlattenRequest<I>, &FlattenRequest<I>::handle_write_header>(this);
+ auto aio_comp = io::AioCompletion::create_and_start(
+ ctx, librbd::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, m_bl.length()}}, io::ImageArea::CRYPTO_HEADER,
+ std::move(m_bl), 0, trace);
+ req->send();
+}
+
+template <typename I>
+void FlattenRequest<I>::handle_write_header(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "error writing header to image: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ flush();
+}
+
+template <typename I>
+void FlattenRequest<I>::flush() {
+ auto ctx = create_context_callback<
+ FlattenRequest<I>, &FlattenRequest<I>::handle_flush>(this);
+ auto aio_comp = io::AioCompletion::create_and_start(
+ ctx, librbd::util::get_image_ctx(m_image_ctx), io::AIO_TYPE_FLUSH);
+ auto req = io::ImageDispatchSpec::create_flush(
+ *m_image_ctx, io::IMAGE_DISPATCH_LAYER_INTERNAL_START, aio_comp,
+ io::FLUSH_SOURCE_INTERNAL, {});
+ req->send();
+}
+
+template <typename I>
+void FlattenRequest<I>::handle_flush(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "unable to flush image: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ finish(r);
+}
+
+template <typename I>
+void FlattenRequest<I>::finish(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::FlattenRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/FlattenRequest.h b/src/librbd/crypto/luks/FlattenRequest.h
new file mode 100644
index 000000000..a1432f505
--- /dev/null
+++ b/src/librbd/crypto/luks/FlattenRequest.h
@@ -0,0 +1,65 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_FLATTEN_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_FLATTEN_REQUEST_H
+
+#include "librbd/ImageCtx.h"
+
+namespace librbd {
+
+namespace crypto {
+namespace luks {
+
+template <typename I>
+class FlattenRequest {
+public:
+ using EncryptionFormat = decltype(I::encryption_format);
+
+ static FlattenRequest* create(I* image_ctx, Context* on_finish) {
+ return new FlattenRequest(image_ctx, on_finish);
+ }
+
+ FlattenRequest(I* image_ctx, Context* on_finish);
+ void send();
+
+private:
+ /**
+ * @verbatim
+ *
+ * <start>
+ * |
+ * v
+ * READ_HEADER
+ * |
+ * v
+ * WRITE_HEADER (replacing magic back from RBDL to LUKS if needed)
+ * |
+ * v
+ * FLUSH
+ * |
+ * v
+ * <finish>
+ *
+ * @endverbatim
+ */
+ I* m_image_ctx;
+ Context* m_on_finish;
+ ceph::bufferlist m_bl;
+
+ void read_header();
+ void handle_read_header(int r);
+ void write_header();
+ void handle_write_header(int r);
+ void flush();
+ void handle_flush(int r);
+ void finish(int r);
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::FlattenRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_FLATTEN_REQUEST_H
diff --git a/src/librbd/crypto/luks/FormatRequest.cc b/src/librbd/crypto/luks/FormatRequest.cc
new file mode 100644
index 000000000..32673b9cf
--- /dev/null
+++ b/src/librbd/crypto/luks/FormatRequest.cc
@@ -0,0 +1,200 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "FormatRequest.h"
+
+#include <stdlib.h>
+#include <openssl/rand.h>
+#include "common/dout.h"
+#include "common/errno.h"
+#include "include/compat.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/Utils.h"
+#include "librbd/crypto/luks/Header.h"
+#include "librbd/crypto/luks/Magic.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.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, encryption_format_t format, encryption_algorithm_t alg,
+ std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto, Context* on_finish,
+ bool insecure_fast_mode) : m_image_ctx(image_ctx), m_format(format),
+ m_alg(alg),
+ m_passphrase(passphrase),
+ m_result_crypto(result_crypto),
+ m_on_finish(on_finish),
+ m_insecure_fast_mode(insecure_fast_mode),
+ m_header(image_ctx->cct) {
+}
+
+template <typename I>
+void FormatRequest<I>::send() {
+ const char* type;
+ size_t sector_size;
+ switch (m_format) {
+ case RBD_ENCRYPTION_FORMAT_LUKS1:
+ type = CRYPT_LUKS1;
+ sector_size = 512;
+ break;
+ case RBD_ENCRYPTION_FORMAT_LUKS2:
+ type = CRYPT_LUKS2;
+ sector_size = 4096;
+ break;
+ default:
+ lderr(m_image_ctx->cct) << "unsupported format type: " << m_format
+ << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ const char* cipher;
+ size_t key_size;
+ switch (m_alg) {
+ case RBD_ENCRYPTION_ALGORITHM_AES128:
+ cipher = "aes";
+ key_size = 32;
+ break;
+ case RBD_ENCRYPTION_ALGORITHM_AES256:
+ cipher = "aes";
+ key_size = 64;
+ break;
+ default:
+ lderr(m_image_ctx->cct) << "unsupported cipher algorithm: " << m_alg
+ << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ // generate encryption key
+ unsigned char* key = (unsigned char*)alloca(key_size);
+ if (RAND_bytes((unsigned char *)key, key_size) != 1) {
+ lderr(m_image_ctx->cct) << "cannot generate random encryption key"
+ << dendl;
+ finish(-EAGAIN);
+ return;
+ }
+
+ // setup interface with libcryptsetup
+ auto r = m_header.init();
+ if (r < 0) {
+ finish(r);
+ return;
+ }
+
+ // format (create LUKS header)
+ auto stripe_period = m_image_ctx->get_stripe_period();
+ r = m_header.format(type, cipher, reinterpret_cast<char*>(key), key_size,
+ "xts-plain64", sector_size, stripe_period,
+ m_insecure_fast_mode);
+ if (r != 0) {
+ finish(r);
+ return;
+ }
+
+ m_image_ctx->image_lock.lock_shared();
+ uint64_t image_size = m_image_ctx->get_image_size(CEPH_NOSNAP);
+ m_image_ctx->image_lock.unlock_shared();
+
+ if (m_header.get_data_offset() > image_size) {
+ lderr(m_image_ctx->cct) << "image is too small, format requires "
+ << m_header.get_data_offset() << " bytes" << dendl;
+ finish(-ENOSPC);
+ return;
+ }
+
+ // add keyslot (volume key encrypted with passphrase)
+ r = m_header.add_keyslot(m_passphrase.data(), m_passphrase.size());
+ if (r != 0) {
+ finish(r);
+ return;
+ }
+
+ r = util::build_crypto(m_image_ctx->cct, key, key_size,
+ m_header.get_sector_size(),
+ m_header.get_data_offset(), m_result_crypto);
+ ceph_memzero_s(key, key_size, key_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;
+ }
+
+ if (m_image_ctx->parent != nullptr) {
+ // parent is not encrypted with same key
+ // change LUKS magic to prevent decryption by other LUKS implementations
+ r = Magic::replace_magic(m_image_ctx->cct, bl);
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "error replacing LUKS magic: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+ }
+
+ // pad header to stripe period alignment to prevent copyup of parent data
+ // when writing encryption header to the child image
+ auto alignment = bl.length() % stripe_period;
+ if (alignment > 0) {
+ bl.append_zero(stripe_period - alignment);
+ }
+
+ // 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, librbd::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()}}, io::ImageArea::DATA, std::move(bl), 0, trace);
+ req->send();
+}
+
+template <typename I>
+void FormatRequest<I>::handle_write_header(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ 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) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/FormatRequest.h b/src/librbd/crypto/luks/FormatRequest.h
new file mode 100644
index 000000000..17d0b3af9
--- /dev/null
+++ b/src/librbd/crypto/luks/FormatRequest.h
@@ -0,0 +1,59 @@
+// -*- 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 <string_view>
+#include "include/rbd/librbd.hpp"
+#include "librbd/ImageCtx.h"
+#include "librbd/crypto/CryptoInterface.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, encryption_format_t format,
+ encryption_algorithm_t alg, std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto, Context* on_finish,
+ bool insecure_fast_mode) {
+ return new FormatRequest(image_ctx, format, alg, passphrase,
+ result_crypto, on_finish, insecure_fast_mode);
+ }
+
+ FormatRequest(I* image_ctx, encryption_format_t format,
+ encryption_algorithm_t alg, std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto,
+ Context* on_finish, bool insecure_fast_mode);
+ void send();
+ void finish(int r);
+
+private:
+ I* m_image_ctx;
+
+ encryption_format_t m_format;
+ encryption_algorithm_t m_alg;
+ std::string_view m_passphrase;
+ std::unique_ptr<CryptoInterface>* m_result_crypto;
+ Context* m_on_finish;
+ bool m_insecure_fast_mode;
+ Header m_header;
+
+ void handle_write_header(int r);
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_FORMAT_REQUEST_H
diff --git a/src/librbd/crypto/luks/Header.cc b/src/librbd/crypto/luks/Header.cc
new file mode 100644
index 000000000..0866f285f
--- /dev/null
+++ b/src/librbd/crypto/luks/Header.cc
@@ -0,0 +1,261 @@
+// -*- 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() {
+ if (m_fd != -1) {
+ return 0;
+ }
+
+ // 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, 30>()) {
+ 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;
+ }
+
+ ldout(m_cct, 20) << "read size = " << r << dendl;
+ return r;
+}
+
+int Header::format(const char* type, const char* alg, const char* key,
+ 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);
+
+ ldout(m_cct, 20) << "sector size: " << sector_size << ", data alignment: "
+ << data_alignment << dendl;
+
+ // 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;
+
+ const size_t converted_data_alignment = data_alignment / 512;
+
+ 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, key, 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(const char* type) {
+ 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, type, NULL);
+ if (r != 0) {
+ ldout(m_cct, 20) << "crypt_load failed: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+
+ ldout(m_cct, 20) << "sector size: " << get_sector_size() << ", data offset: "
+ << get_data_offset() << dendl;
+
+ 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) {
+ ldout(m_cct, 20) << "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);
+}
+
+const char* Header::get_format_name() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_type(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 000000000..067d96b4a
--- /dev/null
+++ b/src/librbd/crypto/luks/Header.h
@@ -0,0 +1,52 @@
+// -*- 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, const char* key,
+ 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(const char* type);
+ 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();
+ const char* get_format_name();
+
+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/LUKSEncryptionFormat.cc b/src/librbd/crypto/luks/LUKSEncryptionFormat.cc
new file mode 100644
index 000000000..1f92cf0f7
--- /dev/null
+++ b/src/librbd/crypto/luks/LUKSEncryptionFormat.cc
@@ -0,0 +1,85 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "LUKSEncryptionFormat.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "include/compat.h"
+#include "librbd/crypto/luks/FlattenRequest.h"
+#include "librbd/crypto/luks/FormatRequest.h"
+#include "librbd/crypto/luks/LoadRequest.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::luks::LUKSEncryptionFormat:: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+template <typename I>
+void EncryptionFormat<I>::flatten(I* image_ctx, Context* on_finish) {
+ auto req = luks::FlattenRequest<I>::create(image_ctx, on_finish);
+ req->send();
+}
+
+template <typename I>
+void LUKSEncryptionFormat<I>::format(I* image_ctx, Context* on_finish) {
+ lderr(image_ctx->cct) << "explicit LUKS version required for format" << dendl;
+ on_finish->complete(-EINVAL);
+}
+
+template <typename I>
+void LUKSEncryptionFormat<I>::load(I* image_ctx,
+ std::string* detected_format_name,
+ Context* on_finish) {
+ auto req = luks::LoadRequest<I>::create(image_ctx, RBD_ENCRYPTION_FORMAT_LUKS,
+ m_passphrase, &this->m_crypto,
+ detected_format_name, on_finish);
+ req->send();
+}
+
+template <typename I>
+void LUKS1EncryptionFormat<I>::format(I* image_ctx, Context* on_finish) {
+ auto req = luks::FormatRequest<I>::create(
+ image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, m_alg, m_passphrase,
+ &this->m_crypto, on_finish, false);
+ req->send();
+}
+
+template <typename I>
+void LUKS1EncryptionFormat<I>::load(I* image_ctx,
+ std::string* detected_format_name,
+ Context* on_finish) {
+ auto req = luks::LoadRequest<I>::create(
+ image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, m_passphrase, &this->m_crypto,
+ detected_format_name, on_finish);
+ req->send();
+}
+
+template <typename I>
+void LUKS2EncryptionFormat<I>::format(I* image_ctx, Context* on_finish) {
+ auto req = luks::FormatRequest<I>::create(
+ image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, m_alg, m_passphrase,
+ &this->m_crypto, on_finish, false);
+ req->send();
+}
+
+template <typename I>
+void LUKS2EncryptionFormat<I>::load(I* image_ctx,
+ std::string* detected_format_name,
+ Context* on_finish) {
+ auto req = luks::LoadRequest<I>::create(
+ image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, m_passphrase, &this->m_crypto,
+ detected_format_name, on_finish);
+ req->send();
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::LUKSEncryptionFormat<librbd::ImageCtx>;
+template class librbd::crypto::luks::LUKS1EncryptionFormat<librbd::ImageCtx>;
+template class librbd::crypto::luks::LUKS2EncryptionFormat<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/LUKSEncryptionFormat.h b/src/librbd/crypto/luks/LUKSEncryptionFormat.h
new file mode 100644
index 000000000..353bd8933
--- /dev/null
+++ b/src/librbd/crypto/luks/LUKSEncryptionFormat.h
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_ENCRYPTION_FORMAT_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_ENCRYPTION_FORMAT_H
+
+#include <string_view>
+#include "include/rbd/librbd.hpp"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/crypto/EncryptionFormat.h"
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace crypto {
+namespace luks {
+
+template <typename ImageCtxT>
+class EncryptionFormat : public crypto::EncryptionFormat<ImageCtxT> {
+public:
+ void flatten(ImageCtxT* ictx, Context* on_finish) override;
+
+ CryptoInterface* get_crypto() override {
+ ceph_assert(m_crypto);
+ return m_crypto.get();
+ }
+
+protected:
+ std::unique_ptr<CryptoInterface> m_crypto;
+};
+
+template <typename ImageCtxT>
+class LUKSEncryptionFormat : public EncryptionFormat<ImageCtxT> {
+public:
+ LUKSEncryptionFormat(std::string_view passphrase)
+ : m_passphrase(passphrase) {}
+
+ std::unique_ptr<crypto::EncryptionFormat<ImageCtxT>> clone() const override {
+ return std::make_unique<LUKSEncryptionFormat>(m_passphrase);
+ }
+
+ void format(ImageCtxT* ictx, Context* on_finish) override;
+ void load(ImageCtxT* ictx, std::string* detected_format_name,
+ Context* on_finish) override;
+
+private:
+ std::string_view m_passphrase;
+};
+
+template <typename ImageCtxT>
+class LUKS1EncryptionFormat : public EncryptionFormat<ImageCtxT> {
+public:
+ LUKS1EncryptionFormat(encryption_algorithm_t alg, std::string_view passphrase)
+ : m_alg(alg), m_passphrase(passphrase) {}
+
+ std::unique_ptr<crypto::EncryptionFormat<ImageCtxT>> clone() const override {
+ return std::make_unique<LUKS1EncryptionFormat>(m_alg, m_passphrase);
+ }
+
+ void format(ImageCtxT* ictx, Context* on_finish) override;
+ void load(ImageCtxT* ictx, std::string* detected_format_name,
+ Context* on_finish) override;
+
+private:
+ encryption_algorithm_t m_alg;
+ std::string_view m_passphrase;
+};
+
+template <typename ImageCtxT>
+class LUKS2EncryptionFormat : public EncryptionFormat<ImageCtxT> {
+public:
+ LUKS2EncryptionFormat(encryption_algorithm_t alg, std::string_view passphrase)
+ : m_alg(alg), m_passphrase(passphrase) {}
+
+ std::unique_ptr<crypto::EncryptionFormat<ImageCtxT>> clone() const override {
+ return std::make_unique<LUKS2EncryptionFormat>(m_alg, m_passphrase);
+ }
+
+ void format(ImageCtxT* ictx, Context* on_finish) override;
+ void load(ImageCtxT* ictx, std::string* detected_format_name,
+ Context* on_finish) override;
+
+private:
+ encryption_algorithm_t m_alg;
+ std::string_view m_passphrase;
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::LUKSEncryptionFormat<
+ librbd::ImageCtx>;
+extern template class librbd::crypto::luks::LUKS1EncryptionFormat<
+ librbd::ImageCtx>;
+extern template class librbd::crypto::luks::LUKS2EncryptionFormat<
+ librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_ENCRYPTION_FORMAT_H
diff --git a/src/librbd/crypto/luks/LoadRequest.cc b/src/librbd/crypto/luks/LoadRequest.cc
new file mode 100644
index 000000000..b5e16f100
--- /dev/null
+++ b/src/librbd/crypto/luks/LoadRequest.cc
@@ -0,0 +1,272 @@
+// -*- 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/Utils.h"
+#include "librbd/crypto/LoadRequest.h"
+#include "librbd/crypto/luks/Magic.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.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, encryption_format_t format, std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto,
+ std::string* detected_format_name,
+ Context* on_finish) : m_image_ctx(image_ctx),
+ m_format(format),
+ m_passphrase(passphrase),
+ m_on_finish(on_finish),
+ m_result_crypto(result_crypto),
+ m_detected_format_name(detected_format_name),
+ m_initial_read_size(DEFAULT_INITIAL_READ_SIZE),
+ m_header(image_ctx->cct), m_offset(0) {
+}
+
+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() {
+ 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, librbd::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::ImageArea::DATA, 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) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "error reading from image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return false;
+ }
+
+ // first, check LUKS magic at the beginning of the image
+ // If no magic is detected, caller may assume image is actually plaintext
+ if (m_offset == 0) {
+ if (Magic::is_luks(m_bl) > 0 || Magic::is_rbd_clone(m_bl) > 0) {
+ *m_detected_format_name = "LUKS";
+ } else {
+ *m_detected_format_name = crypto::LoadRequest<I>::UNKNOWN_FORMAT;
+ finish(-EINVAL);
+ return false;
+ }
+
+ if (m_image_ctx->parent != nullptr && Magic::is_rbd_clone(m_bl) > 0) {
+ r = Magic::replace_magic(m_image_ctx->cct, m_bl);
+ if (r < 0) {
+ m_image_ctx->image_lock.lock_shared();
+ auto image_size = m_image_ctx->get_image_size(m_image_ctx->snap_id);
+ m_image_ctx->image_lock.unlock_shared();
+
+ auto max_header_size = std::min(MAXIMUM_HEADER_SIZE, image_size);
+
+ if (r == -EINVAL && m_bl.length() < max_header_size) {
+ m_bl.clear();
+ auto ctx = create_context_callback<
+ LoadRequest<I>, &LoadRequest<I>::handle_read_header>(this);
+ read(max_header_size, ctx);
+ return false;
+ }
+
+ lderr(m_image_ctx->cct) << "error replacing rbd clone magic: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return false;
+ }
+ }
+ }
+
+ // setup interface with libcryptsetup
+ r = m_header.init();
+ if (r < 0) {
+ finish(r);
+ return false;
+ }
+
+ m_offset += m_bl.length();
+
+ // write header to libcryptsetup interface
+ r = m_header.write(m_bl);
+ if (r < 0) {
+ finish(r);
+ return false;
+ }
+
+ m_bl.clear();
+
+ return true;
+}
+
+template <typename I>
+void LoadRequest<I>::handle_read_header(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ if (!handle_read(r)) {
+ return;
+ }
+
+ const char* type;
+ switch (m_format) {
+ case RBD_ENCRYPTION_FORMAT_LUKS:
+ type = CRYPT_LUKS;
+ break;
+ case RBD_ENCRYPTION_FORMAT_LUKS1:
+ type = CRYPT_LUKS1;
+ break;
+ case RBD_ENCRYPTION_FORMAT_LUKS2:
+ type = CRYPT_LUKS2;
+ break;
+ default:
+ lderr(m_image_ctx->cct) << "unsupported format type: " << m_format
+ << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ // parse header via libcryptsetup
+ r = m_header.load(type);
+ 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;
+ }
+
+ // gets actual LUKS version (only used for logging)
+ ceph_assert(*m_detected_format_name == "LUKS");
+ *m_detected_format_name = m_header.get_format_name();
+
+ 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;
+ }
+
+ m_image_ctx->image_lock.lock_shared();
+ uint64_t image_size = m_image_ctx->get_image_size(CEPH_NOSNAP);
+ m_image_ctx->image_lock.unlock_shared();
+
+ if (m_header.get_data_offset() > image_size) {
+ lderr(m_image_ctx->cct) << "image is too small, data offset "
+ << m_header.get_data_offset() << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ uint64_t stripe_period = m_image_ctx->get_stripe_period();
+ if (m_header.get_data_offset() % stripe_period != 0) {
+ lderr(m_image_ctx->cct) << "incompatible stripe pattern, data offset "
+ << m_header.get_data_offset() << dendl;
+ finish(-EINVAL);
+ return;
+ }
+
+ read_volume_key();
+ return;
+}
+
+template <typename I>
+void LoadRequest<I>::handle_read_keyslots(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ 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.data(), 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;
+ }
+
+ r = util::build_crypto(
+ m_image_ctx->cct, reinterpret_cast<unsigned char*>(volume_key),
+ volume_key_size, m_header.get_sector_size(),
+ m_header.get_data_offset(), m_result_crypto);
+ ceph_memzero_s(volume_key, 64, 64);
+ finish(r);
+}
+
+template <typename I>
+void LoadRequest<I>::finish(int r) {
+ ldout(m_image_ctx->cct, 20) << "r=" << r << dendl;
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/LoadRequest.h b/src/librbd/crypto/luks/LoadRequest.h
new file mode 100644
index 000000000..60ed9a4a4
--- /dev/null
+++ b/src/librbd/crypto/luks/LoadRequest.h
@@ -0,0 +1,71 @@
+// -*- 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 <string_view>
+#include "include/rbd/librbd.hpp"
+#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, encryption_format_t format,
+ std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto,
+ std::string* detected_format_name,
+ Context* on_finish) {
+ return new LoadRequest(image_ctx, format, passphrase, result_crypto,
+ detected_format_name, on_finish);
+ }
+
+ LoadRequest(I* image_ctx, encryption_format_t format,
+ std::string_view passphrase,
+ std::unique_ptr<CryptoInterface>* result_crypto,
+ std::string* detected_format_name, Context* on_finish);
+ void send();
+ void finish(int r);
+ void set_initial_read_size(uint64_t read_size);
+
+private:
+ I* m_image_ctx;
+ encryption_format_t m_format;
+ std::string_view m_passphrase;
+ Context* m_on_finish;
+ ceph::bufferlist m_bl;
+ std::unique_ptr<CryptoInterface>* m_result_crypto;
+ std::string* m_detected_format_name;
+ uint64_t m_initial_read_size;
+ Header m_header;
+ uint64_t m_offset;
+
+ 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
diff --git a/src/librbd/crypto/luks/Magic.cc b/src/librbd/crypto/luks/Magic.cc
new file mode 100644
index 000000000..bc5e19704
--- /dev/null
+++ b/src/librbd/crypto/luks/Magic.cc
@@ -0,0 +1,139 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "Magic.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::Magic: " << __func__ \
+ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+namespace {
+
+constexpr uint64_t MAGIC_LENGTH = 6;
+const std::string LUKS_MAGIC = "LUKS\xba\xbe";
+const std::string RBD_CLONE_MAGIC = "RBDL\xba\xbe";
+
+} // anonymous namespace
+
+int Magic::read(ceph::bufferlist &bl, uint32_t bl_off,
+ uint32_t read_size, char* result) {
+ if (bl_off + read_size > bl.length()) {
+ return -EINVAL;
+ }
+
+ memcpy(result, bl.c_str() + bl_off, read_size);
+ return 0;
+}
+
+int Magic::cmp(ceph::bufferlist &bl, uint32_t bl_off,
+ const std::string &cmp_str) {
+ auto cmp_length = cmp_str.length();
+
+ if (bl_off + cmp_length > bl.length()) {
+ return -EINVAL;
+ }
+
+ if (memcmp(bl.c_str() + bl_off, cmp_str.c_str(), cmp_length)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int Magic::is_luks(ceph::bufferlist& bl) {
+ return cmp(bl, 0, LUKS_MAGIC);
+}
+
+int Magic::is_rbd_clone(ceph::bufferlist& bl) {
+ return cmp(bl, 0, RBD_CLONE_MAGIC);
+}
+
+void Magic::transform_secondary_header_magic(char* magic) {
+ std::swap(magic[0], magic[3]);
+ std::swap(magic[1], magic[2]);
+}
+
+int Magic::replace_magic(CephContext* cct, ceph::bufferlist& bl) {
+ const std::string *old_magic, *new_magic;
+ if (is_luks(bl) > 0) {
+ old_magic = &LUKS_MAGIC;
+ new_magic = &RBD_CLONE_MAGIC;
+ } else if (is_rbd_clone(bl) > 0) {
+ old_magic = &RBD_CLONE_MAGIC;
+ new_magic = &LUKS_MAGIC;
+ } else {
+ lderr(cct) << "invalid magic: " << dendl;
+ return -EILSEQ;
+ }
+
+ // read luks version
+ uint16_t version;
+ auto r = read(bl, MAGIC_LENGTH, sizeof(version), (char*)&version);
+ if (r < 0) {
+ lderr(cct) << "cannot read header version: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ boost::endian::big_to_native_inplace(version);
+
+ switch (version) {
+ case 1: {
+ // LUKS1, no secondary header
+ break;
+ }
+ case 2: {
+ // LUKS2, secondary header follows primary header
+ // read header size
+ uint64_t hdr_size;
+ r = read(bl, MAGIC_LENGTH + sizeof(version), sizeof(hdr_size),
+ (char*)&hdr_size);
+ if (r < 0) {
+ lderr(cct) << "cannot read header size: " << cpp_strerror(r) << dendl;
+ return r;
+ }
+ boost::endian::big_to_native_inplace(hdr_size);
+
+ if ((uint32_t)hdr_size + MAGIC_LENGTH > bl.length()) {
+ ldout(cct, 20) << "cannot replace secondary header magic" << dendl;
+ return -EINVAL;
+ }
+
+ // check secondary header magic
+ auto secondary_header_magic = bl.c_str() + hdr_size;
+ transform_secondary_header_magic(secondary_header_magic);
+ auto is_secondary_header_magic_valid =
+ !memcmp(secondary_header_magic, old_magic->c_str(), MAGIC_LENGTH);
+ if (!is_secondary_header_magic_valid) {
+ transform_secondary_header_magic(secondary_header_magic);
+ lderr(cct) << "invalid secondary header magic" << dendl;
+ return -EILSEQ;
+ }
+
+ // replace secondary header magic
+ memcpy(secondary_header_magic, new_magic->c_str(), MAGIC_LENGTH);
+ transform_secondary_header_magic(secondary_header_magic);
+
+ break;
+ }
+ default: {
+ lderr(cct) << "bad header version: " << version << dendl;
+ return -EINVAL;
+ }
+ }
+
+ // switch primary header magic
+ memcpy(bl.c_str(), new_magic->c_str(), MAGIC_LENGTH);
+
+ return 0;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/luks/Magic.h b/src/librbd/crypto/luks/Magic.h
new file mode 100644
index 000000000..ad06e67f5
--- /dev/null
+++ b/src/librbd/crypto/luks/Magic.h
@@ -0,0 +1,32 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_MAGIC_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_MAGIC_H
+
+#include "common/ceph_context.h"
+#include "include/buffer.h"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+class Magic {
+public:
+ static int is_luks(ceph::bufferlist& bl);
+ static int is_rbd_clone(ceph::bufferlist& bl);
+
+ static int replace_magic(CephContext* cct, ceph::bufferlist& bl);
+private:
+ static int read(ceph::bufferlist& bl, uint32_t bl_off,
+ uint32_t read_size, char* result);
+ static int cmp(ceph::bufferlist& bl, uint32_t bl_off,
+ const std::string& cmp_str);
+ static void transform_secondary_header_magic(char* magic);
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_MAGIC_H