summaryrefslogtreecommitdiffstats
path: root/src/librbd/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'src/librbd/crypto')
-rw-r--r--src/librbd/crypto/BlockCrypto.cc131
-rw-r--r--src/librbd/crypto/BlockCrypto.h60
-rw-r--r--src/librbd/crypto/CryptoContextPool.cc44
-rw-r--r--src/librbd/crypto/CryptoContextPool.h71
-rw-r--r--src/librbd/crypto/CryptoImageDispatch.cc28
-rw-r--r--src/librbd/crypto/CryptoImageDispatch.h111
-rw-r--r--src/librbd/crypto/CryptoInterface.h124
-rw-r--r--src/librbd/crypto/CryptoObjectDispatch.cc661
-rw-r--r--src/librbd/crypto/CryptoObjectDispatch.h115
-rw-r--r--src/librbd/crypto/DataCryptor.h37
-rw-r--r--src/librbd/crypto/EncryptionFormat.h30
-rw-r--r--src/librbd/crypto/FormatRequest.cc119
-rw-r--r--src/librbd/crypto/FormatRequest.h49
-rw-r--r--src/librbd/crypto/LoadRequest.cc74
-rw-r--r--src/librbd/crypto/LoadRequest.h44
-rw-r--r--src/librbd/crypto/ShutDownCryptoRequest.cc102
-rw-r--r--src/librbd/crypto/ShutDownCryptoRequest.h44
-rw-r--r--src/librbd/crypto/Types.h18
-rw-r--r--src/librbd/crypto/Utils.cc73
-rw-r--r--src/librbd/crypto/Utils.h29
-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
-rw-r--r--src/librbd/crypto/openssl/DataCryptor.cc153
-rw-r--r--src/librbd/crypto/openssl/DataCryptor.h49
30 files changed, 3086 insertions, 0 deletions
diff --git a/src/librbd/crypto/BlockCrypto.cc b/src/librbd/crypto/BlockCrypto.cc
new file mode 100644
index 000000000..0b74db04a
--- /dev/null
+++ b/src/librbd/crypto/BlockCrypto.cc
@@ -0,0 +1,131 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/BlockCrypto.h"
+#include "include/byteorder.h"
+#include "include/ceph_assert.h"
+#include "include/scope_guard.h"
+
+#include <stdlib.h>
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+BlockCrypto<T>::BlockCrypto(CephContext* cct, DataCryptor<T>* data_cryptor,
+ uint64_t block_size, uint64_t data_offset)
+ : m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_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);
+ ceph_assert((block_size % 512) == 0);
+}
+
+template <typename T>
+BlockCrypto<T>::~BlockCrypto() {
+ if (m_data_cryptor != nullptr) {
+ delete m_data_cryptor;
+ m_data_cryptor = nullptr;
+ }
+}
+
+template <typename T>
+int BlockCrypto<T>::crypt(ceph::bufferlist* data, uint64_t image_offset,
+ CipherMode mode) {
+ if (image_offset % m_block_size != 0) {
+ lderr(m_cct) << "image offset: " << image_offset
+ << " not aligned to block size: " << m_block_size << dendl;
+ return -EINVAL;
+ }
+ if (data->length() % m_block_size != 0) {
+ lderr(m_cct) << "data length: " << data->length()
+ << " not aligned to block size: " << m_block_size << dendl;
+ return -EINVAL;
+ }
+
+ unsigned char* iv = (unsigned char*)alloca(m_iv_size);
+ memset(iv, 0, m_iv_size);
+
+ bufferlist src = *data;
+ data->clear();
+
+ auto ctx = m_data_cryptor->get_context(mode);
+ if (ctx == nullptr) {
+ lderr(m_cct) << "unable to get crypt context" << dendl;
+ return -EIO;
+ }
+
+ auto sg = make_scope_guard([&] {
+ m_data_cryptor->return_context(ctx, mode); });
+
+ auto sector_number = image_offset / 512;
+ auto appender = data->get_contiguous_appender(src.length());
+ unsigned char* out_buf_ptr = nullptr;
+ unsigned char* leftover_block = (unsigned char*)alloca(m_block_size);
+ uint32_t leftover_size = 0;
+ for (auto buf = src.buffers().begin(); buf != src.buffers().end(); ++buf) {
+ auto in_buf_ptr = reinterpret_cast<const unsigned char*>(buf->c_str());
+ auto remaining_buf_bytes = buf->length();
+ while (remaining_buf_bytes > 0) {
+ if (leftover_size == 0) {
+ auto block_offset_le = init_le64(sector_number);
+ memcpy(iv, &block_offset_le, sizeof(block_offset_le));
+ auto r = m_data_cryptor->init_context(ctx, iv, m_iv_size);
+ if (r != 0) {
+ lderr(m_cct) << "unable to init cipher's IV" << dendl;
+ return r;
+ }
+
+ out_buf_ptr = reinterpret_cast<unsigned char*>(
+ appender.get_pos_add(m_block_size));
+ sector_number += m_block_size / 512;
+ }
+
+ if (leftover_size > 0 || remaining_buf_bytes < m_block_size) {
+ auto copy_size = std::min(
+ (uint32_t)m_block_size - leftover_size, remaining_buf_bytes);
+ memcpy(leftover_block + leftover_size, in_buf_ptr, copy_size);
+ in_buf_ptr += copy_size;
+ leftover_size += copy_size;
+ remaining_buf_bytes -= copy_size;
+ }
+
+ int crypto_output_length = 0;
+ if (leftover_size == 0) {
+ crypto_output_length = m_data_cryptor->update_context(
+ ctx, in_buf_ptr, out_buf_ptr, m_block_size);
+
+ in_buf_ptr += m_block_size;
+ remaining_buf_bytes -= m_block_size;
+ } else if (leftover_size == m_block_size) {
+ crypto_output_length = m_data_cryptor->update_context(
+ ctx, leftover_block, out_buf_ptr, m_block_size);
+ leftover_size = 0;
+ }
+
+ if (crypto_output_length < 0) {
+ lderr(m_cct) << "crypt update failed" << dendl;
+ return crypto_output_length;
+ }
+
+ out_buf_ptr += crypto_output_length;
+ }
+ }
+
+ return 0;
+}
+
+template <typename T>
+int BlockCrypto<T>::encrypt(ceph::bufferlist* data, uint64_t image_offset) {
+ return crypt(data, image_offset, CipherMode::CIPHER_MODE_ENC);
+}
+
+template <typename T>
+int BlockCrypto<T>::decrypt(ceph::bufferlist* data, uint64_t image_offset) {
+ return crypt(data, image_offset, CipherMode::CIPHER_MODE_DEC);
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;
diff --git a/src/librbd/crypto/BlockCrypto.h b/src/librbd/crypto/BlockCrypto.h
new file mode 100644
index 000000000..0bbdd2524
--- /dev/null
+++ b/src/librbd/crypto/BlockCrypto.h
@@ -0,0 +1,60 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
+#define CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
+
+#include "include/Context.h"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/crypto/openssl/DataCryptor.h"
+
+namespace librbd {
+namespace crypto {
+
+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 data_offset);
+ ~BlockCrypto();
+
+ int encrypt(ceph::bufferlist* data, uint64_t image_offset) override;
+ int decrypt(ceph::bufferlist* data, uint64_t image_offset) override;
+
+ uint64_t get_block_size() const override {
+ return m_block_size;
+ }
+
+ uint64_t get_data_offset() const override {
+ return m_data_offset;
+ }
+
+ const unsigned char* get_key() const override {
+ return m_data_cryptor->get_key();
+ }
+
+ int get_key_length() const override {
+ return m_data_cryptor->get_key_length();
+ }
+
+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);
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::BlockCrypto<EVP_CIPHER_CTX>;
+
+#endif //CEPH_LIBRBD_CRYPTO_BLOCK_CRYPTO_H
diff --git a/src/librbd/crypto/CryptoContextPool.cc b/src/librbd/crypto/CryptoContextPool.cc
new file mode 100644
index 000000000..b303a54ec
--- /dev/null
+++ b/src/librbd/crypto/CryptoContextPool.cc
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoContextPool.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+CryptoContextPool<T>::CryptoContextPool(DataCryptor<T>* data_cryptor,
+ uint32_t pool_size)
+ : m_data_cryptor(data_cryptor), m_encrypt_contexts(pool_size),
+ m_decrypt_contexts(pool_size) {
+}
+
+template <typename T>
+CryptoContextPool<T>::~CryptoContextPool() {
+ T* ctx;
+ while (m_encrypt_contexts.pop(ctx)) {
+ m_data_cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC);
+ }
+ while (m_decrypt_contexts.pop(ctx)) {
+ m_data_cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC);
+ }
+}
+
+template <typename T>
+T* CryptoContextPool<T>::get_context(CipherMode mode) {
+ T* ctx;
+ if (!get_contexts(mode).pop(ctx)) {
+ ctx = m_data_cryptor->get_context(mode);
+ }
+ return ctx;
+}
+
+template <typename T>
+void CryptoContextPool<T>::return_context(T* ctx, CipherMode mode) {
+ if (!get_contexts(mode).push(ctx)) {
+ m_data_cryptor->return_context(ctx, mode);
+ }
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/CryptoContextPool.h b/src/librbd/crypto/CryptoContextPool.h
new file mode 100644
index 000000000..c0ebce0c2
--- /dev/null
+++ b/src/librbd/crypto/CryptoContextPool.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_CRYPTO_CONTEXT_POOL_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_CONTEXT_POOL_H
+
+#include "librbd/crypto/DataCryptor.h"
+#include "common/allocator.h"
+#include "include/ceph_assert.h"
+#include <boost/lockfree/queue.hpp>
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+class CryptoContextPool : public DataCryptor<T> {
+
+public:
+ CryptoContextPool(DataCryptor<T>* data_cryptor, uint32_t pool_size);
+ ~CryptoContextPool();
+
+ T* get_context(CipherMode mode) override;
+ void return_context(T* ctx, CipherMode mode) override;
+
+ inline uint32_t get_block_size() const override {
+ return m_data_cryptor->get_block_size();
+ }
+ inline uint32_t get_iv_size() const override {
+ return m_data_cryptor->get_iv_size();
+ }
+ inline int get_key_length() const override {
+ return m_data_cryptor->get_key_length();
+ }
+ inline const unsigned char* get_key() const override {
+ return m_data_cryptor->get_key();
+ }
+ inline int init_context(T* ctx, const unsigned char* iv,
+ uint32_t iv_length) const override {
+ return m_data_cryptor->init_context(ctx, iv, iv_length);
+ }
+ inline int update_context(T* ctx, const unsigned char* in,
+ unsigned char* out,
+ uint32_t len) const override {
+ return m_data_cryptor->update_context(ctx, in, out, len);
+ }
+
+ typedef boost::lockfree::queue<
+ T*,
+ boost::lockfree::allocator<ceph::allocator<void>>> ContextQueue;
+
+private:
+ DataCryptor<T>* m_data_cryptor;
+ ContextQueue m_encrypt_contexts;
+ ContextQueue m_decrypt_contexts;
+
+ inline ContextQueue& get_contexts(CipherMode mode) {
+ switch(mode) {
+ case CIPHER_MODE_ENC:
+ return m_encrypt_contexts;
+ case CIPHER_MODE_DEC:
+ return m_decrypt_contexts;
+ default:
+ ceph_assert(false);
+ }
+ }
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_CONTEXT_POOL_H
diff --git a/src/librbd/crypto/CryptoImageDispatch.cc b/src/librbd/crypto/CryptoImageDispatch.cc
new file mode 100644
index 000000000..15513bf55
--- /dev/null
+++ b/src/librbd/crypto/CryptoImageDispatch.cc
@@ -0,0 +1,28 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoImageDispatch.h"
+
+namespace librbd {
+namespace crypto {
+
+CryptoImageDispatch::CryptoImageDispatch(
+ uint64_t data_offset) : m_data_offset(data_offset) {
+}
+
+
+void CryptoImageDispatch::remap_extents(
+ io::Extents& image_extents, io::ImageExtentsMapType type) {
+ if (type == io::IMAGE_EXTENTS_MAP_TYPE_LOGICAL_TO_PHYSICAL) {
+ for (auto& extent: image_extents) {
+ extent.first += m_data_offset;
+ }
+ } else if (type == io::IMAGE_EXTENTS_MAP_TYPE_PHYSICAL_TO_LOGICAL) {
+ for (auto& extent: image_extents) {
+ extent.first -= m_data_offset;
+ }
+ }
+}
+
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/CryptoImageDispatch.h b/src/librbd/crypto/CryptoImageDispatch.h
new file mode 100644
index 000000000..dae3dac85
--- /dev/null
+++ b/src/librbd/crypto/CryptoImageDispatch.h
@@ -0,0 +1,111 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_IMAGE_DISPATCH_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_IMAGE_DISPATCH_H
+
+#include "librbd/io/ImageDispatchInterface.h"
+
+namespace librbd {
+namespace crypto {
+
+class CryptoImageDispatch : public io::ImageDispatchInterface {
+public:
+ static CryptoImageDispatch* create(uint64_t data_offset) {
+ return new CryptoImageDispatch(data_offset);
+ }
+ CryptoImageDispatch(uint64_t data_offset);
+
+ io::ImageDispatchLayer get_dispatch_layer() const override {
+ return io::IMAGE_DISPATCH_LAYER_CRYPTO;
+ }
+
+ void shut_down(Context* on_finish) override {
+ on_finish->complete(0);
+ }
+
+ bool read(
+ io::AioCompletion* aio_comp, io::Extents &&image_extents,
+ io::ReadResult &&read_result, IOContext io_context, int op_flags,
+ int read_flags, const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool write(
+ io::AioCompletion* aio_comp, io::Extents &&image_extents,
+ bufferlist &&bl, IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool discard(
+ io::AioCompletion* aio_comp, io::Extents &&image_extents,
+ uint32_t discard_granularity_bytes, IOContext io_context,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool write_same(
+ io::AioCompletion* aio_comp, io::Extents &&image_extents,
+ bufferlist &&bl, IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool compare_and_write(
+ io::AioCompletion* aio_comp, io::Extents &&image_extents,
+ bufferlist &&cmp_bl, bufferlist &&bl, uint64_t *mismatch_offset,
+ IOContext io_context, int op_flags, const ZTracer::Trace &parent_trace,
+ uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool flush(
+ io::AioCompletion* aio_comp, io::FlushSource flush_source,
+ const ZTracer::Trace &parent_trace, uint64_t tid,
+ std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool list_snaps(
+ io::AioCompletion* aio_comp, io::Extents&& image_extents,
+ io::SnapIds&& snap_ids, int list_snaps_flags,
+ io::SnapshotDelta* snapshot_delta, const ZTracer::Trace &parent_trace,
+ uint64_t tid, std::atomic<uint32_t>* image_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool invalidate_cache(Context* on_finish) override {
+ return false;
+ }
+
+ void remap_extents(io::Extents& image_extents,
+ io::ImageExtentsMapType type) override;
+
+private:
+ uint64_t m_data_offset;
+
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_IMAGE_DISPATCH_H
diff --git a/src/librbd/crypto/CryptoInterface.h b/src/librbd/crypto/CryptoInterface.h
new file mode 100644
index 000000000..170a5bf28
--- /dev/null
+++ b/src/librbd/crypto/CryptoInterface.h
@@ -0,0 +1,124 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
+
+#include "common/RefCountedObj.h"
+#include "include/buffer.h"
+#include "include/intarith.h"
+#include "librbd/io/Types.h"
+
+namespace librbd {
+namespace crypto {
+
+class CryptoInterface : public RefCountedObject {
+
+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;
+ virtual const unsigned char* get_key() const = 0;
+ virtual int get_key_length() const = 0;
+
+ inline std::pair<uint64_t, uint64_t> get_pre_and_post_align(
+ uint64_t off, uint64_t len) {
+ if (len == 0) {
+ return std::make_pair(0, 0);
+ }
+ auto block_size = get_block_size();
+ return std::make_pair(p2phase(off, block_size),
+ p2nphase(off + len, block_size));
+ }
+
+ inline std::pair<uint64_t, uint64_t> align(uint64_t off, uint64_t len) {
+ auto aligns = get_pre_and_post_align(off, len);
+ return std::make_pair(off - aligns.first,
+ len + aligns.first + aligns.second);
+ }
+
+ inline bool is_aligned(uint64_t off, uint64_t len) {
+ auto aligns = get_pre_and_post_align(off, len);
+ return aligns.first == 0 && aligns.second == 0;
+ }
+
+ inline bool is_aligned(const io::ReadExtents& extents) {
+ for (const auto& extent: extents) {
+ if (!is_aligned(extent.offset, extent.length)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline void align_extents(const io::ReadExtents& extents,
+ io::ReadExtents* aligned_extents) {
+ for (const auto& extent: extents) {
+ auto aligned = align(extent.offset, extent.length);
+ aligned_extents->emplace_back(aligned.first, aligned.second);
+ }
+ }
+
+ inline int decrypt_aligned_extent(io::ReadExtent& extent,
+ uint64_t image_offset) {
+ if (extent.length == 0 || extent.bl.length() == 0) {
+ return 0;
+ }
+
+ if (extent.extent_map.empty()) {
+ extent.extent_map.emplace_back(extent.offset, extent.bl.length());
+ }
+
+ ceph::bufferlist result_bl;
+ io::Extents result_extent_map;
+
+ ceph::bufferlist curr_block_bl;
+ auto curr_offset = extent.offset;
+ auto curr_block_start_offset = curr_offset;
+ auto curr_block_end_offset = curr_offset;
+
+ // this will add a final loop iteration for decrypting the last extent
+ extent.extent_map.emplace_back(
+ extent.offset + extent.length + get_block_size(), 0);
+
+ for (auto [off, len]: extent.extent_map) {
+ auto [aligned_off, aligned_len] = align(off, len);
+ if (aligned_off > curr_block_end_offset) {
+ curr_block_bl.append_zero(curr_block_end_offset - curr_offset);
+ auto curr_block_length = curr_block_bl.length();
+ if (curr_block_length > 0) {
+ auto r = decrypt(
+ &curr_block_bl,
+ image_offset + curr_block_start_offset - extent.offset);
+ if (r != 0) {
+ return r;
+ }
+
+ curr_block_bl.splice(0, curr_block_length, &result_bl);
+ result_extent_map.emplace_back(
+ curr_block_start_offset, curr_block_length);
+ }
+
+ curr_block_start_offset = aligned_off;
+ curr_block_end_offset = aligned_off + aligned_len;
+ curr_offset = aligned_off;
+ }
+
+ curr_block_bl.append_zero(off - curr_offset);
+ extent.bl.splice(0, len, &curr_block_bl);
+ curr_offset = off + len;
+ curr_block_end_offset = aligned_off + aligned_len;
+ }
+
+ extent.bl = std::move(result_bl);
+ extent.extent_map = std::move(result_extent_map);
+
+ return 0;
+ }
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_INTERFACE_H
diff --git a/src/librbd/crypto/CryptoObjectDispatch.cc b/src/librbd/crypto/CryptoObjectDispatch.cc
new file mode 100644
index 000000000..244f52dec
--- /dev/null
+++ b/src/librbd/crypto/CryptoObjectDispatch.cc
@@ -0,0 +1,661 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "include/ceph_assert.h"
+#include "include/neorados/RADOS.hpp"
+#include "common/dout.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+#include "librbd/io/ObjectDispatchSpec.h"
+#include "librbd/io/ReadResult.h"
+#include "librbd/io/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::CryptoObjectDispatch: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+
+using librbd::util::create_context_callback;
+using librbd::util::data_object_name;
+
+template <typename I>
+struct C_AlignedObjectReadRequest : public Context {
+ I* image_ctx;
+ ceph::ref_t<CryptoInterface> crypto;
+ uint64_t object_no;
+ io::ReadExtents* extents;
+ IOContext io_context;
+ const ZTracer::Trace parent_trace;
+ uint64_t* version;
+ Context* on_finish;
+ io::ObjectDispatchSpec* req;
+ bool disable_read_from_parent;
+
+ C_AlignedObjectReadRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
+ uint64_t* version, int* object_dispatch_flags,
+ Context* on_dispatched
+ ) : image_ctx(image_ctx), crypto(crypto), object_no(object_no),
+ extents(extents), io_context(io_context),
+ parent_trace(parent_trace), version(version),
+ on_finish(on_dispatched) {
+ disable_read_from_parent =
+ ((read_flags & io::READ_FLAG_DISABLE_READ_FROM_PARENT) != 0);
+ read_flags |= io::READ_FLAG_DISABLE_READ_FROM_PARENT;
+
+ auto ctx = create_context_callback<
+ C_AlignedObjectReadRequest<I>,
+ &C_AlignedObjectReadRequest<I>::handle_read>(this);
+
+ req = io::ObjectDispatchSpec::create_read(
+ image_ctx, io::OBJECT_DISPATCH_LAYER_CRYPTO, object_no,
+ extents, io_context, op_flags, read_flags, parent_trace,
+ version, ctx);
+ }
+
+ void send() {
+ req->send();
+ }
+
+ void finish(int r) override {
+ ldout(image_ctx->cct, 20) << "aligned read r=" << r << dendl;
+ on_finish->complete(r);
+ }
+
+ void handle_read(int r) {
+ auto cct = image_ctx->cct;
+ ldout(cct, 20) << "aligned read r=" << r << dendl;
+ if (r == 0) {
+ for (auto& extent: *extents) {
+ auto crypto_ret = crypto->decrypt_aligned_extent(
+ extent,
+ io::util::get_file_offset(
+ image_ctx, object_no, extent.offset));
+ if (crypto_ret != 0) {
+ ceph_assert(crypto_ret < 0);
+ r = crypto_ret;
+ break;
+ }
+ r += extent.length;
+ }
+ }
+
+ if (r == -ENOENT && !disable_read_from_parent) {
+ io::util::read_parent<I>(
+ image_ctx, object_no, extents,
+ io_context->read_snap().value_or(CEPH_NOSNAP),
+ parent_trace, this);
+ } else {
+ complete(r);
+ }
+ }
+};
+
+template <typename I>
+struct C_UnalignedObjectReadRequest : public Context {
+ CephContext* cct;
+ io::ReadExtents* extents;
+ Context* on_finish;
+ io::ReadExtents aligned_extents;
+ io::ObjectDispatchSpec* req;
+
+ C_UnalignedObjectReadRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
+ uint64_t* version, int* object_dispatch_flags,
+ Context* on_dispatched) : cct(image_ctx->cct), extents(extents),
+ on_finish(on_dispatched) {
+ crypto->align_extents(*extents, &aligned_extents);
+
+ // send the aligned read back to get decrypted
+ req = io::ObjectDispatchSpec::create_read(
+ image_ctx,
+ io::util::get_previous_layer(io::OBJECT_DISPATCH_LAYER_CRYPTO),
+ object_no, &aligned_extents, io_context, op_flags, read_flags,
+ parent_trace, version, this);
+ }
+
+ void send() {
+ req->send();
+ }
+
+ void remove_alignment_data() {
+ for (uint64_t i = 0; i < extents->size(); ++i) {
+ auto& extent = (*extents)[i];
+ auto& aligned_extent = aligned_extents[i];
+ if (aligned_extent.extent_map.empty()) {
+ uint64_t cut_offset = extent.offset - aligned_extent.offset;
+ int64_t padding_count =
+ cut_offset + extent.length - aligned_extent.bl.length();
+ if (padding_count > 0) {
+ aligned_extent.bl.append_zero(padding_count);
+ }
+ aligned_extent.bl.splice(cut_offset, extent.length, &extent.bl);
+ } else {
+ for (auto [off, len]: aligned_extent.extent_map) {
+ ceph::bufferlist tmp;
+ aligned_extent.bl.splice(0, len, &tmp);
+
+ uint64_t bytes_to_skip = 0;
+ if (off < extent.offset) {
+ bytes_to_skip = extent.offset - off;
+ if (len <= bytes_to_skip) {
+ continue;
+ }
+ off += bytes_to_skip;
+ len -= bytes_to_skip;
+ }
+
+ len = std::min(len, extent.offset + extent.length - off);
+ if (len == 0) {
+ continue;
+ }
+
+ if (len > 0) {
+ tmp.splice(bytes_to_skip, len, &extent.bl);
+ extent.extent_map.emplace_back(off, len);
+ }
+ }
+ }
+ }
+ }
+
+ void finish(int r) override {
+ ldout(cct, 20) << "unaligned read r=" << r << dendl;
+ if (r >= 0) {
+ remove_alignment_data();
+
+ r = 0;
+ for (auto& extent: *extents) {
+ r += extent.length;
+ }
+ }
+ on_finish->complete(r);
+ }
+};
+
+template <typename I>
+struct C_UnalignedObjectWriteRequest : public Context {
+ I* image_ctx;
+ ceph::ref_t<CryptoInterface> crypto;
+ uint64_t object_no;
+ uint64_t object_off;
+ ceph::bufferlist data;
+ ceph::bufferlist cmp_data;
+ uint64_t* mismatch_offset;
+ IOContext io_context;
+ int op_flags;
+ int write_flags;
+ std::optional<uint64_t> assert_version;
+ const ZTracer::Trace parent_trace;
+ int* object_dispatch_flags;
+ uint64_t* journal_tid;
+ Context* on_finish;
+ bool may_copyup;
+ ceph::bufferlist aligned_data;
+ io::ReadExtents extents;
+ uint64_t version;
+ C_UnalignedObjectReadRequest<I>* read_req;
+ bool object_exists;
+
+ C_UnalignedObjectWriteRequest(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto,
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ ceph::bufferlist&& cmp_data, uint64_t* mismatch_offset,
+ IOContext io_context, int op_flags, int write_flags,
+ std::optional<uint64_t> assert_version,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, Context* on_dispatched, bool may_copyup
+ ) : image_ctx(image_ctx), crypto(crypto), object_no(object_no),
+ object_off(object_off), data(data), cmp_data(cmp_data),
+ mismatch_offset(mismatch_offset), io_context(io_context),
+ op_flags(op_flags), write_flags(write_flags),
+ assert_version(assert_version), parent_trace(parent_trace),
+ object_dispatch_flags(object_dispatch_flags),
+ journal_tid(journal_tid), on_finish(on_dispatched),
+ may_copyup(may_copyup) {
+ // build read extents
+ auto [pre_align, post_align] = crypto->get_pre_and_post_align(
+ object_off, data.length());
+ if (pre_align != 0) {
+ extents.emplace_back(object_off - pre_align, pre_align);
+ }
+ if (post_align != 0) {
+ extents.emplace_back(object_off + data.length(), post_align);
+ }
+ if (cmp_data.length() != 0) {
+ extents.emplace_back(object_off, cmp_data.length());
+ }
+
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_read>(this);
+
+ read_req = new C_UnalignedObjectReadRequest<I>(
+ image_ctx, crypto, object_no, &extents, io_context,
+ 0, io::READ_FLAG_DISABLE_READ_FROM_PARENT, parent_trace,
+ &version, 0, ctx);
+ }
+
+ void send() {
+ read_req->send();
+ }
+
+ bool check_cmp_data() {
+ if (cmp_data.length() == 0) {
+ return true;
+ }
+
+ auto& cmp_extent = extents.back();
+ io::util::unsparsify(image_ctx->cct, &cmp_extent.bl,
+ cmp_extent.extent_map, cmp_extent.offset,
+ cmp_extent.length);
+
+ std::optional<uint64_t> found_mismatch = std::nullopt;
+
+ auto it1 = cmp_data.cbegin();
+ auto it2 = cmp_extent.bl.cbegin();
+ for (uint64_t idx = 0; idx < cmp_data.length(); ++idx) {
+ if (*it1 != *it2) {
+ found_mismatch = std::make_optional(idx);
+ break;
+ }
+ ++it1;
+ ++it2;
+ }
+
+ extents.pop_back();
+
+ if (found_mismatch.has_value()) {
+ if (mismatch_offset != nullptr) {
+ *mismatch_offset = found_mismatch.value();
+ }
+ complete(-EILSEQ);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool check_create_exclusive() {
+ bool exclusive =
+ ((write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE) != 0);
+ if (exclusive && object_exists) {
+ complete(-EEXIST);
+ return false;
+ }
+ return true;
+ }
+
+ bool check_version() {
+ int r = 0;
+ if (assert_version.has_value()) {
+ if (!object_exists) {
+ r = -ENOENT;
+ } else if (assert_version.value() < version) {
+ r = -ERANGE;
+ } else if (assert_version.value() > version) {
+ r = -EOVERFLOW;
+ }
+ }
+
+ if (r != 0) {
+ complete(r);
+ return false;
+ }
+ return true;
+ }
+
+ void build_aligned_data() {
+ auto [pre_align, post_align] = crypto->get_pre_and_post_align(
+ object_off, data.length());
+ if (pre_align != 0) {
+ auto &extent = extents.front();
+ io::util::unsparsify(image_ctx->cct, &extent.bl, extent.extent_map,
+ extent.offset, extent.length);
+ extent.bl.splice(0, pre_align, &aligned_data);
+ }
+ aligned_data.append(data);
+ if (post_align != 0) {
+ auto &extent = extents.back();
+ io::util::unsparsify(image_ctx->cct, &extent.bl, extent.extent_map,
+ extent.offset, extent.length);
+ extent.bl.splice(0, post_align, &aligned_data);
+ }
+ }
+
+ void handle_copyup(int r) {
+ ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+ if (r < 0) {
+ complete(r);
+ } else {
+ restart_request(false);
+ }
+ }
+
+ void handle_read(int r) {
+ ldout(image_ctx->cct, 20) << "unaligned write r=" << r << dendl;
+
+ if (r == -ENOENT) {
+ if (may_copyup) {
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_copyup>(this);
+ if (io::util::trigger_copyup(
+ image_ctx, object_no, io_context, ctx)) {
+ return;
+ }
+ delete ctx;
+ }
+ object_exists = false;
+ } else if (r < 0) {
+ complete(r);
+ return;
+ } else {
+ object_exists = true;
+ }
+
+ if (!check_create_exclusive() || !check_version() || !check_cmp_data()) {
+ return;
+ }
+
+ build_aligned_data();
+
+ auto aligned_off = crypto->align(object_off, data.length()).first;
+ auto new_write_flags = write_flags;
+ auto new_assert_version = std::make_optional(version);
+ if (!object_exists) {
+ new_write_flags |= io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE;
+ new_assert_version = std::nullopt;
+ }
+
+ auto ctx = create_context_callback<
+ C_UnalignedObjectWriteRequest<I>,
+ &C_UnalignedObjectWriteRequest<I>::handle_write>(this);
+
+ // send back aligned write back to get encrypted and committed
+ auto write_req = io::ObjectDispatchSpec::create_write(
+ image_ctx,
+ io::util::get_previous_layer(io::OBJECT_DISPATCH_LAYER_CRYPTO),
+ object_no, aligned_off, std::move(aligned_data), io_context,
+ op_flags, new_write_flags, new_assert_version,
+ journal_tid == nullptr ? 0 : *journal_tid, parent_trace, ctx);
+ write_req->send();
+ }
+
+ void restart_request(bool may_copyup) {
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ image_ctx, crypto, object_no, object_off,
+ std::move(data), std::move(cmp_data),
+ mismatch_offset, io_context, op_flags, write_flags,
+ assert_version, parent_trace,
+ object_dispatch_flags, journal_tid, this, may_copyup);
+ req->send();
+ }
+
+ void handle_write(int r) {
+ ldout(image_ctx->cct, 20) << "r=" << r << dendl;
+ bool exclusive = write_flags & io::OBJECT_WRITE_FLAG_CREATE_EXCLUSIVE;
+ bool restart = false;
+ if (r == -ERANGE && !assert_version.has_value()) {
+ restart = true;
+ } else if (r == -EEXIST && !exclusive) {
+ restart = true;
+ }
+
+ if (restart) {
+ restart_request(may_copyup);
+ } else {
+ complete(r);
+ }
+ }
+
+ void finish(int r) override {
+ ldout(image_ctx->cct, 20) << "unaligned write r=" << r << dendl;
+ on_finish->complete(r);
+ }
+};
+
+template <typename I>
+CryptoObjectDispatch<I>::CryptoObjectDispatch(
+ I* image_ctx, ceph::ref_t<CryptoInterface> crypto)
+ : m_image_ctx(image_ctx), m_crypto(crypto) {
+}
+
+template <typename I>
+void CryptoObjectDispatch<I>::shut_down(Context* on_finish) {
+ if (m_crypto != nullptr) {
+ m_crypto->put();
+ m_crypto = nullptr;
+ }
+ on_finish->complete(0);
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::read(
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
+ uint64_t* version, int* object_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << *extents << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ if (m_crypto->is_aligned(*extents)) {
+ auto req = new C_AlignedObjectReadRequest<I>(
+ m_image_ctx, m_crypto, object_no, extents, io_context,
+ op_flags, read_flags, parent_trace, version, object_dispatch_flags,
+ on_dispatched);
+ req->send();
+ } else {
+ auto req = new C_UnalignedObjectReadRequest<I>(
+ m_image_ctx, m_crypto, object_no, extents, io_context,
+ op_flags, read_flags, parent_trace, version, object_dispatch_flags,
+ on_dispatched);
+ req->send();
+ }
+
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ IOContext io_context, int op_flags, int write_flags,
+ std::optional<uint64_t> assert_version,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << data.length() << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ if (m_crypto->is_aligned(object_off, data.length())) {
+ auto r = m_crypto->encrypt(
+ &data,
+ io::util::get_file_offset(m_image_ctx, object_no, object_off));
+ *dispatch_result = r == 0 ? io::DISPATCH_RESULT_CONTINUE
+ : io::DISPATCH_RESULT_COMPLETE;
+ on_dispatched->complete(r);
+ } else {
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ m_image_ctx, m_crypto, object_no, object_off, std::move(data), {},
+ nullptr, io_context, op_flags, write_flags, assert_version,
+ parent_trace, object_dispatch_flags, journal_tid, on_dispatched,
+ true);
+ req->send();
+ }
+
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::write_same(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ io::LightweightBufferExtents&& buffer_extents, ceph::bufferlist&& data,
+ IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << object_len << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ // convert to regular write
+ io::LightweightObjectExtent extent(object_no, object_off, object_len, 0);
+ extent.buffer_extents = std::move(buffer_extents);
+
+ bufferlist ws_data;
+ io::util::assemble_write_same_extent(extent, data, &ws_data, true);
+
+ auto ctx = new LambdaContext(
+ [on_finish_ctx=on_dispatched](int r) {
+ on_finish_ctx->complete(r);
+ });
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = io::ObjectDispatchSpec::create_write(
+ m_image_ctx,
+ io::util::get_previous_layer(io::OBJECT_DISPATCH_LAYER_CRYPTO),
+ object_no, object_off, std::move(ws_data), io_context, op_flags, 0,
+ std::nullopt, 0, parent_trace, ctx);
+ req->send();
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::compare_and_write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& cmp_data,
+ ceph::bufferlist&& write_data, IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t* mismatch_offset,
+ int* object_dispatch_flags, uint64_t* journal_tid,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << write_data.length()
+ << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = new C_UnalignedObjectWriteRequest<I>(
+ m_image_ctx, m_crypto, object_no, object_off, std::move(write_data),
+ std::move(cmp_data), mismatch_offset, io_context, op_flags, 0,
+ std::nullopt, parent_trace, object_dispatch_flags, journal_tid,
+ on_dispatched, true);
+ req->send();
+
+ return true;
+}
+
+template <typename I>
+bool CryptoObjectDispatch<I>::discard(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ IOContext io_context, int discard_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) {
+ auto cct = m_image_ctx->cct;
+ ldout(cct, 20) << data_object_name(m_image_ctx, object_no) << " "
+ << object_off << "~" << object_len << dendl;
+ ceph_assert(m_crypto != nullptr);
+
+ // convert to write-same
+ auto ctx = new LambdaContext(
+ [on_finish_ctx=on_dispatched](int r) {
+ on_finish_ctx->complete(r);
+ });
+
+ bufferlist bl;
+ const int buffer_size = 4096;
+ bl.append_zero(buffer_size);
+
+ *dispatch_result = io::DISPATCH_RESULT_COMPLETE;
+ auto req = io::ObjectDispatchSpec::create_write_same(
+ m_image_ctx,
+ io::util::get_previous_layer(io::OBJECT_DISPATCH_LAYER_CRYPTO),
+ object_no, object_off, object_len, {{0, object_len}}, std::move(bl),
+ io_context, *object_dispatch_flags, 0, parent_trace, ctx);
+ req->send();
+ return true;
+}
+
+template <typename I>
+int CryptoObjectDispatch<I>::prepare_copyup(
+ uint64_t object_no,
+ io::SnapshotSparseBufferlist* snapshot_sparse_bufferlist) {
+ ceph::bufferlist current_bl;
+ current_bl.append_zero(m_image_ctx->get_object_size());
+
+ for (auto& [key, extent_map]: *snapshot_sparse_bufferlist) {
+ // update current_bl with data from extent_map
+ for (auto& extent : extent_map) {
+ auto &sbe = extent.get_val();
+ if (sbe.state == io::SPARSE_EXTENT_STATE_DATA) {
+ current_bl.begin(extent.get_off()).copy_in(extent.get_len(), sbe.bl);
+ } else if (sbe.state == io::SPARSE_EXTENT_STATE_ZEROED) {
+ ceph::bufferlist zeros;
+ zeros.append_zero(extent.get_len());
+ current_bl.begin(extent.get_off()).copy_in(extent.get_len(), zeros);
+ }
+ }
+
+ // encrypt
+ io::SparseBufferlist encrypted_sparse_bufferlist;
+ for (auto& extent : extent_map) {
+ auto [aligned_off, aligned_len] = m_crypto->align(
+ extent.get_off(), extent.get_len());
+
+ io::Extents image_extents;
+ io::util::extent_to_file(
+ m_image_ctx, object_no, aligned_off, aligned_len, image_extents);
+
+ ceph::bufferlist encrypted_bl;
+ uint64_t position = 0;
+ for (auto [image_offset, image_length]: image_extents) {
+ ceph::bufferlist aligned_bl;
+ aligned_bl.substr_of(current_bl, aligned_off + position, image_length);
+ aligned_bl.rebuild(); // to deep copy aligned_bl from current_bl
+ position += image_length;
+
+ auto r = m_crypto->encrypt(&aligned_bl, image_offset);
+ if (r != 0) {
+ return r;
+ }
+
+ encrypted_bl.append(aligned_bl);
+ }
+
+ encrypted_sparse_bufferlist.insert(
+ aligned_off, aligned_len, {io::SPARSE_EXTENT_STATE_DATA, aligned_len,
+ std::move(encrypted_bl)});
+ }
+
+ // replace original plaintext sparse bufferlist with encrypted one
+ extent_map.clear();
+ extent_map.insert(std::move(encrypted_sparse_bufferlist));
+ }
+
+ return 0;
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::CryptoObjectDispatch<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/CryptoObjectDispatch.h b/src/librbd/crypto/CryptoObjectDispatch.h
new file mode 100644
index 000000000..1c5a4646d
--- /dev/null
+++ b/src/librbd/crypto/CryptoObjectDispatch.h
@@ -0,0 +1,115 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
+#define CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
+
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/io/Types.h"
+#include "librbd/io/ObjectDispatchInterface.h"
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace crypto {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+class CryptoObjectDispatch : public io::ObjectDispatchInterface {
+public:
+ static CryptoObjectDispatch* create(
+ ImageCtxT* image_ctx, ceph::ref_t<CryptoInterface> crypto) {
+ return new CryptoObjectDispatch(image_ctx, crypto);
+ }
+
+ CryptoObjectDispatch(ImageCtxT* image_ctx,
+ ceph::ref_t<CryptoInterface> crypto);
+
+ io::ObjectDispatchLayer get_dispatch_layer() const override {
+ return io::OBJECT_DISPATCH_LAYER_CRYPTO;
+ }
+
+ void shut_down(Context* on_finish) override;
+
+ bool read(
+ uint64_t object_no, io::ReadExtents* extents, IOContext io_context,
+ int op_flags, int read_flags, const ZTracer::Trace &parent_trace,
+ uint64_t* version, int* object_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override;
+
+ bool discard(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ IOContext io_context, int discard_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& data,
+ IOContext io_context, int op_flags, int write_flags,
+ std::optional<uint64_t> assert_version,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool write_same(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ io::LightweightBufferExtents&& buffer_extents, ceph::bufferlist&& data,
+ IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, int* object_dispatch_flags,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override;
+
+ bool compare_and_write(
+ uint64_t object_no, uint64_t object_off, ceph::bufferlist&& cmp_data,
+ ceph::bufferlist&& write_data, IOContext io_context, int op_flags,
+ const ZTracer::Trace &parent_trace, uint64_t* mismatch_offset,
+ int* object_dispatch_flags, uint64_t* journal_tid,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override;
+
+ bool flush(
+ io::FlushSource flush_source, const ZTracer::Trace &parent_trace,
+ uint64_t* journal_tid, io::DispatchResult* dispatch_result,
+ Context** on_finish, Context* on_dispatched) override {
+ return false;
+ }
+
+ bool list_snaps(
+ uint64_t object_no, io::Extents&& extents, io::SnapIds&& snap_ids,
+ int list_snap_flags, const ZTracer::Trace &parent_trace,
+ io::SnapshotDelta* snapshot_delta, int* object_dispatch_flags,
+ io::DispatchResult* dispatch_result, Context** on_finish,
+ Context* on_dispatched) override {
+ return false;
+ }
+
+ bool invalidate_cache(Context* on_finish) override {
+ return false;
+ }
+ bool reset_existence_cache(Context* on_finish) override {
+ return false;
+ }
+
+ void extent_overwritten(
+ uint64_t object_no, uint64_t object_off, uint64_t object_len,
+ uint64_t journal_tid, uint64_t new_journal_tid) override {
+ }
+
+ int prepare_copyup(
+ uint64_t object_no,
+ io::SnapshotSparseBufferlist* snapshot_sparse_bufferlist) override;
+
+private:
+ ImageCtxT* m_image_ctx;
+ ceph::ref_t<CryptoInterface> m_crypto;
+
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::CryptoObjectDispatch<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_CRYPTO_OBJECT_DISPATCH_H
diff --git a/src/librbd/crypto/DataCryptor.h b/src/librbd/crypto/DataCryptor.h
new file mode 100644
index 000000000..ffcc57ce4
--- /dev/null
+++ b/src/librbd/crypto/DataCryptor.h
@@ -0,0 +1,37 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
+#define CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
+
+#include "include/int_types.h"
+#include "librbd/crypto/Types.h"
+
+namespace librbd {
+namespace crypto {
+
+template <typename T>
+class DataCryptor {
+
+public:
+
+ virtual ~DataCryptor() = default;
+
+ virtual uint32_t get_block_size() const = 0;
+ virtual uint32_t get_iv_size() const = 0;
+ virtual const unsigned char* get_key() const = 0;
+ virtual int get_key_length() const = 0;
+
+ virtual T* get_context(CipherMode mode) = 0;
+ virtual void return_context(T* ctx, CipherMode mode) = 0;
+
+ virtual int init_context(T* ctx, const unsigned char* iv,
+ uint32_t iv_length) const = 0;
+ virtual int update_context(T* ctx, const unsigned char* in,
+ unsigned char* out, uint32_t len) const = 0;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
diff --git a/src/librbd/crypto/EncryptionFormat.h b/src/librbd/crypto/EncryptionFormat.h
new file mode 100644
index 000000000..ba57a9252
--- /dev/null
+++ b/src/librbd/crypto/EncryptionFormat.h
@@ -0,0 +1,30 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_ENCRYPTION_FORMAT_H
+#define CEPH_LIBRBD_CRYPTO_ENCRYPTION_FORMAT_H
+
+#include "common/ref.h"
+
+struct Context;
+
+namespace librbd {
+namespace crypto {
+
+struct CryptoInterface;
+
+template <typename ImageCtxT>
+struct EncryptionFormat {
+ virtual ~EncryptionFormat() {
+ }
+
+ virtual void format(ImageCtxT* ictx, Context* on_finish) = 0;
+ virtual void load(ImageCtxT* ictx, Context* on_finish) = 0;
+
+ virtual ceph::ref_t<CryptoInterface> get_crypto() = 0;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_ENCRYPTION_FORMAT_H
diff --git a/src/librbd/crypto/FormatRequest.cc b/src/librbd/crypto/FormatRequest.cc
new file mode 100644
index 000000000..53dda58aa
--- /dev/null
+++ b/src/librbd/crypto/FormatRequest.cc
@@ -0,0 +1,119 @@
+// -*- 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/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/ShutDownCryptoRequest.h"
+#include "librbd/crypto/Utils.h"
+#include "librbd/io/AioCompletion.h"
+#include "librbd/io/ImageDispatchSpec.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+#include "librbd/io/Types.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::FormatRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+FormatRequest<I>::FormatRequest(
+ I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish) : m_image_ctx(image_ctx),
+ m_format(std::move(format)),
+ m_on_finish(on_finish) {
+}
+
+template <typename I>
+void FormatRequest<I>::send() {
+ if (m_image_ctx->test_features(RBD_FEATURE_JOURNALING)) {
+ lderr(m_image_ctx->cct) << "cannot use encryption with journal" << dendl;
+ finish(-ENOTSUP);
+ return;
+ }
+
+ if (m_image_ctx->crypto == nullptr) {
+ format();
+ return;
+ }
+
+ auto ctx = create_context_callback<
+ FormatRequest<I>, &FormatRequest<I>::handle_shutdown_crypto>(this);
+ auto *req = ShutDownCryptoRequest<I>::create(m_image_ctx, ctx);
+ req->send();
+}
+
+template <typename I>
+void FormatRequest<I>::handle_shutdown_crypto(int r) {
+ if (r != 0) {
+ lderr(m_image_ctx->cct) << "unable to unload existing crypto: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ format();
+}
+
+template <typename I>
+void FormatRequest<I>::format() {
+ auto ctx = create_context_callback<
+ FormatRequest<I>, &FormatRequest<I>::handle_format>(this);
+ m_format->format(m_image_ctx, ctx);
+}
+
+template <typename I>
+void FormatRequest<I>::handle_format(int r) {
+ if (r != 0) {
+ lderr(m_image_ctx->cct) << "unable to format image: " << cpp_strerror(r)
+ << dendl;
+ finish(r);
+ return;
+ }
+
+ flush();
+}
+
+template <typename I>
+void FormatRequest<I>::flush() {
+ auto ctx = create_context_callback<
+ FormatRequest<I>, &FormatRequest<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 FormatRequest<I>::handle_flush(int r) {
+ if (r != 0) {
+ lderr(m_image_ctx->cct) << "unable to flush image: " << cpp_strerror(r)
+ << dendl;
+ }
+
+ finish(r);
+}
+
+template <typename I>
+void FormatRequest<I>::finish(int r) {
+ if (r == 0) {
+ util::set_crypto(m_image_ctx, m_format->get_crypto());
+ }
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::FormatRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/FormatRequest.h b/src/librbd/crypto/FormatRequest.h
new file mode 100644
index 000000000..cfd7978d8
--- /dev/null
+++ b/src/librbd/crypto/FormatRequest.h
@@ -0,0 +1,49 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_FORMAT_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_FORMAT_REQUEST_H
+
+#include "include/rbd/librbd.hpp"
+#include "librbd/crypto/EncryptionFormat.h"
+
+struct Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace crypto {
+
+template <typename I>
+class FormatRequest {
+public:
+ static FormatRequest* create(
+ I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish) {
+ return new FormatRequest(image_ctx, std::move(format), on_finish);
+ }
+
+ FormatRequest(I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish);
+ void send();
+ void handle_shutdown_crypto(int r);
+ void format();
+ void handle_format(int r);
+ void flush();
+ void handle_flush(int r);
+ void finish(int r);
+
+private:
+ I* m_image_ctx;
+
+ std::unique_ptr<EncryptionFormat<I>> m_format;
+ Context* m_on_finish;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::FormatRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_FORMAT_REQUEST_H
diff --git a/src/librbd/crypto/LoadRequest.cc b/src/librbd/crypto/LoadRequest.cc
new file mode 100644
index 000000000..c42011f62
--- /dev/null
+++ b/src/librbd/crypto/LoadRequest.cc
@@ -0,0 +1,74 @@
+// -*- 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/ImageCtx.h"
+#include "librbd/crypto/Utils.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::LoadRequest: " << this \
+ << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+LoadRequest<I>::LoadRequest(
+ I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish) : m_image_ctx(image_ctx),
+ m_format(std::move(format)),
+ m_on_finish(on_finish) {
+}
+
+template <typename I>
+void LoadRequest<I>::send() {
+ if (m_image_ctx->crypto != nullptr) {
+ lderr(m_image_ctx->cct) << "encryption already loaded" << dendl;
+ finish(-EEXIST);
+ return;
+ }
+
+ auto ictx = m_image_ctx;
+ while (ictx != nullptr) {
+ if (ictx->test_features(RBD_FEATURE_JOURNALING)) {
+ lderr(m_image_ctx->cct) << "cannot use encryption with journal."
+ << " image name: " << ictx->name << dendl;
+ finish(-ENOTSUP);
+ return;
+ }
+ ictx = ictx->parent;
+ }
+
+ auto ctx = create_context_callback<
+ LoadRequest<I>, &LoadRequest<I>::finish>(this);
+ m_format->load(m_image_ctx, ctx);
+}
+
+template <typename I>
+void LoadRequest<I>::finish(int r) {
+
+ if (r == 0) {
+ // load crypto layers to image and its ancestors
+ auto crypto = m_format->get_crypto();
+ auto ictx = m_image_ctx;
+ while (ictx != nullptr) {
+ util::set_crypto(ictx, crypto);
+ ictx = ictx->parent;
+ }
+ }
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::LoadRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/LoadRequest.h b/src/librbd/crypto/LoadRequest.h
new file mode 100644
index 000000000..50d1dad84
--- /dev/null
+++ b/src/librbd/crypto/LoadRequest.h
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_LOAD_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_LOAD_REQUEST_H
+
+#include "include/rbd/librbd.hpp"
+#include "librbd/crypto/CryptoInterface.h"
+#include "librbd/crypto/EncryptionFormat.h"
+
+struct Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace crypto {
+
+template <typename I>
+class LoadRequest {
+public:
+ static LoadRequest* create(
+ I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish) {
+ return new LoadRequest(image_ctx, std::move(format), on_finish);
+ }
+
+ LoadRequest(I* image_ctx, std::unique_ptr<EncryptionFormat<I>> format,
+ Context* on_finish);
+ void send();
+ void finish(int r);
+
+private:
+ I* m_image_ctx;
+ std::unique_ptr<EncryptionFormat<I>> m_format;
+ Context* m_on_finish;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::LoadRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_LOAD_REQUEST_H
diff --git a/src/librbd/crypto/ShutDownCryptoRequest.cc b/src/librbd/crypto/ShutDownCryptoRequest.cc
new file mode 100644
index 000000000..9277308e4
--- /dev/null
+++ b/src/librbd/crypto/ShutDownCryptoRequest.cc
@@ -0,0 +1,102 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "ShutDownCryptoRequest.h"
+
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/Utils.h"
+#include "librbd/crypto/CryptoImageDispatch.h"
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "librbd/io/ImageDispatcherInterface.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::ShutDownCryptoRequest: " \
+ << this << " " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+
+using librbd::util::create_context_callback;
+
+template <typename I>
+ShutDownCryptoRequest<I>::ShutDownCryptoRequest(
+ I* image_ctx, Context* on_finish) : m_image_ctx(image_ctx),
+ m_on_finish(on_finish) {
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::send() {
+ shut_down_object_dispatch();
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::shut_down_object_dispatch() {
+ if (!m_image_ctx->io_object_dispatcher->exists(
+ io::OBJECT_DISPATCH_LAYER_CRYPTO)) {
+ finish(0);
+ return;
+ }
+
+ auto ctx = create_context_callback<
+ ShutDownCryptoRequest<I>,
+ &ShutDownCryptoRequest<I>::handle_shut_down_object_dispatch>(this);
+
+ m_image_ctx->io_object_dispatcher->shut_down_dispatch(
+ io::OBJECT_DISPATCH_LAYER_CRYPTO, ctx);
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::handle_shut_down_object_dispatch(int r) {
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "failed to shut down object dispatch: "
+ << cpp_strerror(r) << dendl;
+ finish(r);
+ return;
+ }
+
+ shut_down_image_dispatch();
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::shut_down_image_dispatch() {
+ if (!m_image_ctx->io_image_dispatcher->exists(
+ io::IMAGE_DISPATCH_LAYER_CRYPTO)) {
+ finish(0);
+ return;
+ }
+
+ auto ctx = create_context_callback<
+ ShutDownCryptoRequest<I>,
+ &ShutDownCryptoRequest<I>::handle_shut_down_image_dispatch>(this);
+ m_image_ctx->io_image_dispatcher->shut_down_dispatch(
+ io::IMAGE_DISPATCH_LAYER_CRYPTO, ctx);
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::handle_shut_down_image_dispatch(int r) {
+ if (r < 0) {
+ lderr(m_image_ctx->cct) << "failed to shut down image dispatch: "
+ << cpp_strerror(r) << dendl;
+ }
+ finish(r);
+}
+
+template <typename I>
+void ShutDownCryptoRequest<I>::finish(int r) {
+ if (r == 0) {
+ std::unique_lock image_locker{m_image_ctx->image_lock};
+ m_image_ctx->crypto = nullptr;
+ }
+
+ m_on_finish->complete(r);
+ delete this;
+}
+
+} // namespace crypto
+} // namespace librbd
+
+template class librbd::crypto::ShutDownCryptoRequest<librbd::ImageCtx>;
diff --git a/src/librbd/crypto/ShutDownCryptoRequest.h b/src/librbd/crypto/ShutDownCryptoRequest.h
new file mode 100644
index 000000000..cf402c1b4
--- /dev/null
+++ b/src/librbd/crypto/ShutDownCryptoRequest.h
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_SHUT_DOWN_CRYPTO_REQUEST_H
+#define CEPH_LIBRBD_CRYPTO_SHUT_DOWN_CRYPTO_REQUEST_H
+
+#include "librbd/crypto/CryptoInterface.h"
+
+struct Context;
+
+namespace librbd {
+
+class ImageCtx;
+
+namespace crypto {
+
+class CryptoInterface;
+
+template <typename I>
+class ShutDownCryptoRequest {
+public:
+ static ShutDownCryptoRequest* create(I* image_ctx, Context* on_finish) {
+ return new ShutDownCryptoRequest(image_ctx, on_finish);
+ }
+
+ ShutDownCryptoRequest(I* image_ctx, Context* on_finish);
+ void send();
+ void shut_down_object_dispatch();
+ void handle_shut_down_object_dispatch(int r);
+ void shut_down_image_dispatch();
+ void handle_shut_down_image_dispatch(int r);
+ void finish(int r);
+
+private:
+ I* m_image_ctx;
+ Context* m_on_finish;
+};
+
+} // namespace crypto
+} // namespace librbd
+
+extern template class librbd::crypto::ShutDownCryptoRequest<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_CRYPTO_SHUT_DOWN_CRYPTO_REQUEST_H
diff --git a/src/librbd/crypto/Types.h b/src/librbd/crypto/Types.h
new file mode 100644
index 000000000..93d9c172c
--- /dev/null
+++ b/src/librbd/crypto/Types.h
@@ -0,0 +1,18 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_TYPES_H
+#define CEPH_LIBRBD_CRYPTO_TYPES_H
+
+namespace librbd {
+namespace crypto {
+
+enum CipherMode {
+ CIPHER_MODE_ENC,
+ CIPHER_MODE_DEC,
+};
+
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_DATA_CRYPTOR_H
diff --git a/src/librbd/crypto/Utils.cc b/src/librbd/crypto/Utils.cc
new file mode 100644
index 000000000..76cc9f1f5
--- /dev/null
+++ b/src/librbd/crypto/Utils.cc
@@ -0,0 +1,73 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "Utils.h"
+
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/crypto/BlockCrypto.h"
+#include "librbd/crypto/CryptoImageDispatch.h"
+#include "librbd/crypto/CryptoObjectDispatch.h"
+#include "librbd/crypto/openssl/DataCryptor.h"
+#include "librbd/io/ImageDispatcherInterface.h"
+#include "librbd/io/ObjectDispatcherInterface.h"
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::crypto::util: " << __func__ << ": "
+
+namespace librbd {
+namespace crypto {
+namespace util {
+
+template <typename I>
+void set_crypto(I *image_ctx, ceph::ref_t<CryptoInterface> crypto) {
+ {
+ std::unique_lock image_locker{image_ctx->image_lock};
+ ceph_assert(image_ctx->crypto == nullptr);
+ image_ctx->crypto = crypto.get();
+ }
+ auto object_dispatch = CryptoObjectDispatch<I>::create(image_ctx, crypto);
+ auto image_dispatch = CryptoImageDispatch::create(crypto->get_data_offset());
+ image_ctx->io_object_dispatcher->register_dispatch(object_dispatch);
+ image_ctx->io_image_dispatcher->register_dispatch(image_dispatch);
+}
+
+int build_crypto(
+ CephContext* cct, const unsigned char* key, uint32_t key_length,
+ uint64_t block_size, uint64_t data_offset,
+ ceph::ref_t<CryptoInterface>* result_crypto) {
+ const char* cipher_suite;
+ switch (key_length) {
+ case 32:
+ cipher_suite = "aes-128-xts";
+ break;
+ case 64:
+ cipher_suite = "aes-256-xts";
+ break;
+ default:
+ lderr(cct) << "unsupported key length: " << key_length << dendl;
+ return -ENOTSUP;
+ }
+
+ auto data_cryptor = new openssl::DataCryptor(cct);
+ int r = data_cryptor->init(cipher_suite, key, key_length);
+ if (r != 0) {
+ lderr(cct) << "error initializing data cryptor: " << cpp_strerror(r)
+ << dendl;
+ delete data_cryptor;
+ return r;
+ }
+
+ *result_crypto = BlockCrypto<EVP_CIPHER_CTX>::create(
+ cct, data_cryptor, block_size, data_offset);
+ return 0;
+}
+
+} // namespace util
+} // namespace crypto
+} // namespace librbd
+
+template void librbd::crypto::util::set_crypto(
+ librbd::ImageCtx *image_ctx, ceph::ref_t<CryptoInterface> crypto);
diff --git a/src/librbd/crypto/Utils.h b/src/librbd/crypto/Utils.h
new file mode 100644
index 000000000..aed3d4767
--- /dev/null
+++ b/src/librbd/crypto/Utils.h
@@ -0,0 +1,29 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_UTILS_H
+#define CEPH_LIBRBD_CRYPTO_UTILS_H
+
+#include "include/Context.h"
+#include "librbd/crypto/CryptoInterface.h"
+
+namespace librbd {
+
+struct ImageCtx;
+
+namespace crypto {
+namespace util {
+
+template <typename ImageCtxT = librbd::ImageCtx>
+void set_crypto(ImageCtxT *image_ctx, ceph::ref_t<CryptoInterface> crypto);
+
+int build_crypto(
+ CephContext* cct, const unsigned char* key, uint32_t key_length,
+ uint64_t block_size, uint64_t data_offset,
+ ceph::ref_t<CryptoInterface>* result_crypto);
+
+} // namespace util
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_UTILS_H
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
diff --git a/src/librbd/crypto/openssl/DataCryptor.cc b/src/librbd/crypto/openssl/DataCryptor.cc
new file mode 100644
index 000000000..aa9427a79
--- /dev/null
+++ b/src/librbd/crypto/openssl/DataCryptor.cc
@@ -0,0 +1,153 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/crypto/openssl/DataCryptor.h"
+#include <openssl/err.h>
+#include <string.h>
+#include "include/ceph_assert.h"
+#include "include/compat.h"
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+int DataCryptor::init(const char* cipher_name, const unsigned char* key,
+ uint16_t key_length) {
+ if (m_key != nullptr) {
+ ceph_memzero_s(m_key, m_key_size, m_key_size);
+ delete [] m_key;
+ m_key = nullptr;
+ m_key_size = 0;
+ }
+ if (cipher_name == nullptr) {
+ lderr(m_cct) << "missing cipher name" << dendl;
+ return -EINVAL;
+ }
+ if (key == nullptr) {
+ lderr(m_cct) << "missing key" << dendl;
+ return -EINVAL;
+ }
+
+ m_cipher = EVP_get_cipherbyname(cipher_name);
+ if (m_cipher == nullptr) {
+ lderr(m_cct) << "EVP_get_cipherbyname failed. Cipher name: " << cipher_name
+ << dendl;
+ log_errors();
+ return -EINVAL;
+ }
+
+ auto expected_key_length = EVP_CIPHER_key_length(m_cipher);
+ if (expected_key_length != key_length) {
+ lderr(m_cct) << "cipher " << cipher_name << " expects key of "
+ << expected_key_length << " bytes. got: " << key_length
+ << dendl;
+ return -EINVAL;
+ }
+
+ m_key_size = key_length;
+ m_key = new unsigned char[key_length];
+ memcpy(m_key, key, key_length);
+ m_iv_size = static_cast<uint32_t>(EVP_CIPHER_iv_length(m_cipher));
+ return 0;
+}
+
+DataCryptor::~DataCryptor() {
+ if (m_key != nullptr) {
+ ceph_memzero_s(m_key, m_key_size, m_key_size);
+ delete [] m_key;
+ m_key = nullptr;
+ }
+}
+
+uint32_t DataCryptor::get_block_size() const {
+ return EVP_CIPHER_block_size(m_cipher);
+}
+
+uint32_t DataCryptor::get_iv_size() const {
+ return m_iv_size;
+}
+
+const unsigned char* DataCryptor::get_key() const {
+ return m_key;
+}
+
+int DataCryptor::get_key_length() const {
+ return EVP_CIPHER_key_length(m_cipher);
+}
+
+EVP_CIPHER_CTX* DataCryptor::get_context(CipherMode mode) {
+ int enc;
+ switch(mode) {
+ case CIPHER_MODE_ENC:
+ enc = 1;
+ break;
+ case CIPHER_MODE_DEC:
+ enc = 0;
+ break;
+ default:
+ lderr(m_cct) << "Invalid CipherMode:" << mode << dendl;
+ return nullptr;
+ }
+
+ auto ctx = EVP_CIPHER_CTX_new();
+ if (ctx == nullptr) {
+ lderr(m_cct) << "EVP_CIPHER_CTX_new failed" << dendl;
+ log_errors();
+ return nullptr;
+ }
+
+ if (1 != EVP_CipherInit_ex(ctx, m_cipher, nullptr, m_key, nullptr, enc)) {
+ lderr(m_cct) << "EVP_CipherInit_ex failed" << dendl;
+ log_errors();
+ return nullptr;
+ }
+
+ return ctx;
+}
+
+void DataCryptor::return_context(EVP_CIPHER_CTX* ctx, CipherMode mode) {
+ if (ctx != nullptr) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
+}
+
+int DataCryptor::init_context(EVP_CIPHER_CTX* ctx, const unsigned char* iv,
+ uint32_t iv_length) const {
+ if (iv_length != m_iv_size) {
+ lderr(m_cct) << "cipher expects IV of " << m_iv_size << " bytes. got: "
+ << iv_length << dendl;
+ return -EINVAL;
+ }
+ if (1 != EVP_CipherInit_ex(ctx, nullptr, nullptr, nullptr, iv, -1)) {
+ lderr(m_cct) << "EVP_CipherInit_ex failed" << dendl;
+ log_errors();
+ return -EIO;
+ }
+ return 0;
+}
+
+int DataCryptor::update_context(EVP_CIPHER_CTX* ctx, const unsigned char* in,
+ unsigned char* out, uint32_t len) const {
+ int out_length;
+ if (1 != EVP_CipherUpdate(ctx, out, &out_length, in, len)) {
+ lderr(m_cct) << "EVP_CipherUpdate failed. len=" << len << dendl;
+ log_errors();
+ return -EIO;
+ }
+ return out_length;
+}
+
+void DataCryptor::log_errors() const {
+ while (true) {
+ auto error = ERR_get_error();
+ if (error == 0) {
+ break;
+ }
+ lderr(m_cct) << "OpenSSL error: " << ERR_error_string(error, nullptr)
+ << dendl;
+ }
+}
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
diff --git a/src/librbd/crypto/openssl/DataCryptor.h b/src/librbd/crypto/openssl/DataCryptor.h
new file mode 100644
index 000000000..af6956883
--- /dev/null
+++ b/src/librbd/crypto/openssl/DataCryptor.h
@@ -0,0 +1,49 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H
+#define CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H
+
+#include "librbd/crypto/DataCryptor.h"
+#include "include/Context.h"
+#include <openssl/evp.h>
+
+namespace librbd {
+namespace crypto {
+namespace openssl {
+
+class DataCryptor : public crypto::DataCryptor<EVP_CIPHER_CTX> {
+
+public:
+ DataCryptor(CephContext* cct) : m_cct(cct) {};
+ ~DataCryptor();
+
+ int init(const char* cipher_name, const unsigned char* key,
+ uint16_t key_length);
+ uint32_t get_block_size() const override;
+ uint32_t get_iv_size() const override;
+ const unsigned char* get_key() const override;
+ int get_key_length() const override;
+
+ EVP_CIPHER_CTX* get_context(CipherMode mode) override;
+ void return_context(EVP_CIPHER_CTX* ctx, CipherMode mode) override;
+ int init_context(EVP_CIPHER_CTX* ctx, const unsigned char* iv,
+ uint32_t iv_length) const override;
+ int update_context(EVP_CIPHER_CTX* ctx, const unsigned char* in,
+ unsigned char* out, uint32_t len) const override;
+
+private:
+ CephContext* m_cct;
+ unsigned char* m_key = nullptr;
+ uint16_t m_key_size = 0;
+ const EVP_CIPHER* m_cipher;
+ uint32_t m_iv_size;
+
+ void log_errors() const;
+};
+
+} // namespace openssl
+} // namespace crypto
+} // namespace librbd
+
+#endif // CEPH_LIBRBD_CRYPTO_OPENSSL_DATA_CRYPTOR_H