diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/librbd/crypto | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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 |