summaryrefslogtreecommitdiffstats
path: root/src/librbd/crypto/luks
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/librbd/crypto/luks/EncryptionFormat.cc48
-rw-r--r--src/librbd/crypto/luks/EncryptionFormat.h67
-rw-r--r--src/librbd/crypto/luks/FormatRequest.cc178
-rw-r--r--src/librbd/crypto/luks/FormatRequest.h58
-rw-r--r--src/librbd/crypto/luks/Header.cc256
-rw-r--r--src/librbd/crypto/luks/Header.h51
-rw-r--r--src/librbd/crypto/luks/LoadRequest.cc196
-rw-r--r--src/librbd/crypto/luks/LoadRequest.h66
8 files changed, 920 insertions, 0 deletions
diff --git a/src/librbd/crypto/luks/EncryptionFormat.cc b/src/librbd/crypto/luks/EncryptionFormat.cc
new file mode 100644
index 000000000..8b1b1580c
--- /dev/null
+++ b/src/librbd/crypto/luks/EncryptionFormat.cc
@@ -0,0 +1,48 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "EncryptionFormat.h"
+#include "include/compat.h"
+#include "librbd/crypto/luks/FormatRequest.h"
+#include "librbd/crypto/luks/LoadRequest.h"
+
+namespace librbd {
+namespace crypto {
+namespace luks {
+
+template <typename I>
+EncryptionFormat<I>::EncryptionFormat(
+ encryption_algorithm_t alg,
+ std::string&& passphrase) : m_alg(alg),
+ m_passphrase(std::move(passphrase)) {
+}
+
+template <typename I>
+EncryptionFormat<I>::~EncryptionFormat() {
+ ceph_memzero_s(
+ &m_passphrase[0], m_passphrase.capacity(), m_passphrase.size());
+}
+
+template <typename I>
+void EncryptionFormat<I>::format(I* image_ctx, Context* on_finish) {
+ auto req = luks::FormatRequest<I>::create(
+ image_ctx, get_format(), m_alg, std::move(m_passphrase), &m_crypto,
+ on_finish, false);
+ req->send();
+}
+
+template <typename I>
+void EncryptionFormat<I>::load(I* image_ctx, Context* on_finish) {
+ auto req = luks::LoadRequest<I>::create(
+ image_ctx, get_format(), std::move(m_passphrase), &m_crypto,
+ on_finish);
+ req->send();
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::EncryptionFormat<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/EncryptionFormat.h b/src/librbd/crypto/luks/EncryptionFormat.h
new file mode 100644
index 000000000..8c45cf9ca
--- /dev/null
+++ b/src/librbd/crypto/luks/EncryptionFormat.h
@@ -0,0 +1,67 @@
+// -*- 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 "include/rbd/librbd.hpp"
+#include "librbd/crypto/EncryptionFormat.h"
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace crypto {
+namespace luks {
+
+template <typename ImageCtxT>
+class EncryptionFormat : public crypto::EncryptionFormat<ImageCtxT> {
+
+public:
+ EncryptionFormat(encryption_algorithm_t alg, std::string&& passphrase);
+ ~EncryptionFormat();
+
+ void format(ImageCtxT* ictx, Context* on_finish) override;
+ void load(ImageCtxT* ictx, Context* on_finish) override;
+
+ ceph::ref_t<CryptoInterface> get_crypto() override {
+ return m_crypto;
+ }
+
+private:
+ virtual encryption_format_t get_format() = 0;
+
+ encryption_algorithm_t m_alg;
+ std::string m_passphrase;
+ ceph::ref_t<CryptoInterface> m_crypto;
+};
+
+template <typename ImageCtxT>
+class LUKS1EncryptionFormat : public EncryptionFormat<ImageCtxT> {
+ using EncryptionFormat<ImageCtxT>::EncryptionFormat;
+
+ encryption_format_t get_format() override {
+ return RBD_ENCRYPTION_FORMAT_LUKS1;
+ }
+};
+
+template <typename ImageCtxT>
+class LUKS2EncryptionFormat : public EncryptionFormat<ImageCtxT> {
+ using EncryptionFormat<ImageCtxT>::EncryptionFormat;
+
+ encryption_format_t get_format() override {
+ return RBD_ENCRYPTION_FORMAT_LUKS2;
+ }
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::luks::EncryptionFormat<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/FormatRequest.cc b/src/librbd/crypto/luks/FormatRequest.cc
new file mode 100644
index 000000000..be2452fd9
--- /dev/null
+++ b/src/librbd/crypto/luks/FormatRequest.cc
@@ -0,0 +1,178 @@
+// -*- 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/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&& passphrase, ceph::ref_t<CryptoInterface>* result_crypto,
+ Context* on_finish,
+ bool insecure_fast_mode) : m_image_ctx(image_ctx), m_format(format),
+ m_alg(alg),
+ m_passphrase(std::move(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)
+ r = m_header.format(type, cipher, reinterpret_cast<char*>(key), key_size,
+ "xts-plain64", sector_size,
+ m_image_ctx->get_object_size(), 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 more than "
+ << m_header.get_data_offset() << " bytes" << dendl;
+ finish(-ENOSPC);
+ 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;
+ }
+
+ 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;
+ }
+
+ // 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()}}, 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) {
+ ceph_memzero_s(
+ &m_passphrase[0], m_passphrase.capacity(), m_passphrase.size());
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::FormatRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/FormatRequest.h b/src/librbd/crypto/luks/FormatRequest.h
new file mode 100644
index 000000000..a782dc818
--- /dev/null
+++ b/src/librbd/crypto/luks/FormatRequest.h
@@ -0,0 +1,58 @@
+// -*- 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 "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&& passphrase,
+ ceph::ref_t<CryptoInterface>* result_crypto, Context* on_finish,
+ bool insecure_fast_mode) {
+ return new FormatRequest(image_ctx, format, alg, std::move(passphrase),
+ result_crypto, on_finish, insecure_fast_mode);
+ }
+
+ FormatRequest(I* image_ctx, encryption_format_t format,
+ encryption_algorithm_t alg, std::string&& passphrase,
+ ceph::ref_t<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 m_passphrase;
+ ceph::ref_t<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..6d00074ef
--- /dev/null
+++ b/src/librbd/crypto/luks/Header.cc
@@ -0,0 +1,256 @@
+// -*- 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;
+ }
+
+ 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;
+
+#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, 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) {
+ lderr(m_cct) << "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) {
+ lderr(m_cct) << "crypt_volume_key_get failed: " << cpp_strerror(r)
+ << dendl;
+ return r;
+ }
+
+ return 0;
+}
+
+int Header::get_sector_size() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_sector_size(m_cd);
+}
+
+uint64_t Header::get_data_offset() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_data_offset(m_cd) << 9;
+}
+
+const char* Header::get_cipher() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_cipher(m_cd);
+}
+
+const char* Header::get_cipher_mode() {
+ ceph_assert(m_cd != nullptr);
+ return crypt_get_cipher_mode(m_cd);
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/luks/Header.h b/src/librbd/crypto/luks/Header.h
new file mode 100644
index 000000000..cee80a8e4
--- /dev/null
+++ b/src/librbd/crypto/luks/Header.h
@@ -0,0 +1,51 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
+#define CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
+
+#include <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();
+
+private:
+ void libcryptsetup_log(int level, const char* msg);
+ static void libcryptsetup_log_wrapper(int level, const char* msg,
+ void* header);
+
+ CephContext* m_cct;
+ int m_fd;
+ struct crypt_device *m_cd;
+};
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_LUKS_HEADER_H
diff --git a/src/librbd/crypto/luks/LoadRequest.cc b/src/librbd/crypto/luks/LoadRequest.cc
new file mode 100644
index 000000000..10339469d
--- /dev/null
+++ b/src/librbd/crypto/luks/LoadRequest.cc
@@ -0,0 +1,196 @@
+// -*- 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/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&& passphrase,
+ ceph::ref_t<CryptoInterface>* result_crypto,
+ Context* on_finish) : m_image_ctx(image_ctx),
+ m_format(format),
+ m_passphrase(std::move(passphrase)),
+ 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) {
+}
+
+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() {
+ // 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, 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::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;
+ }
+
+ const char* type;
+ switch (m_format) {
+ 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;
+ }
+
+ 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;
+ }
+
+ 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);
+ finish(r);
+}
+
+template <typename I>
+void LoadRequest<I>::finish(int r) {
+ ceph_memzero_s(&m_passphrase[0], m_passphrase.size(), m_passphrase.size());
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace luks
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::luks::LoadRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/luks/LoadRequest.h b/src/librbd/crypto/luks/LoadRequest.h
new file mode 100644
index 000000000..340e89503
--- /dev/null
+++ b/src/librbd/crypto/luks/LoadRequest.h
@@ -0,0 +1,66 @@
+// -*- 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 "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&& passphrase,
+ ceph::ref_t<CryptoInterface>* result_crypto, Context* on_finish) {
+ return new LoadRequest(image_ctx, format, std::move(passphrase),
+ result_crypto, on_finish);
+ }
+
+ LoadRequest(I* image_ctx, encryption_format_t format,
+ 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;
+ encryption_format_t m_format;
+ std::string m_passphrase;
+ 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;
+
+ 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