From 6467ebb806ccd5e4909a2d609d8185cea3fbe2e1 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Sat, 21 Feb 2026 12:34:05 +0300 Subject: [PATCH] WIP Add AES-XTS client encryption support --- src/client/CMakeLists.txt | 7 +- src/client/cluster_client.cpp | 17 +- src/client/cluster_client.h | 1 + src/client/etcd_state_client.cpp | 11 ++ src/client/etcd_state_client.h | 10 + src/client/messenger.cpp | 13 ++ src/client/messenger.h | 21 ++ src/client/msgr_encrypt.cpp | 328 +++++++++++++++++++++++++++++++ src/client/msgr_encrypt.h | 44 +++++ src/client/msgr_op.cpp | 4 + src/client/msgr_op.h | 6 + src/client/msgr_receive.cpp | 21 +- src/client/msgr_send.cpp | 42 +++- src/client/msgr_stop.cpp | 16 ++ src/test/test_cluster_client.cpp | 70 +++++++ 15 files changed, 594 insertions(+), 17 deletions(-) create mode 100644 src/client/msgr_encrypt.cpp create mode 100644 src/client/msgr_encrypt.h diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index d757a52c..07027b24 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -13,10 +13,10 @@ if (RDMACM_LIBRARIES) endif (RDMACM_LIBRARIES) add_library(vitastor_common STATIC ../util/epoll_manager.cpp etcd_state_client.cpp messenger.cpp ../util/addr_util.cpp - msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp + msgr_encrypt.cpp msgr_stop.cpp msgr_op.cpp msgr_send.cpp msgr_receive.cpp ../util/ringloop.cpp ../../json11/json11.cpp http_client.cpp osd_ops.cpp pg_states.cpp ../util/timerfd_manager.cpp ../util/str_util.cpp ../util/json_util.cpp ${MSGR_RDMA} ${MSGR_RDMACM} ) -target_link_libraries(vitastor_common pthread) +target_link_libraries(vitastor_common pthread ${OPENSSL_LIBRARIES}) target_compile_options(vitastor_common PUBLIC -fPIC) # libvitastor_client.so @@ -99,9 +99,10 @@ endif (${WITH_QEMU}) add_executable(test_cluster_client EXCLUDE_FROM_ALL ../test/test_cluster_client.cpp - pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp + pg_states.cpp osd_ops.cpp cluster_client.cpp cluster_client_list.cpp cluster_client_wb.cpp msgr_op.cpp ../test/mock/messenger.cpp msgr_stop.cpp msgr_encrypt.cpp etcd_state_client.cpp ../util/timerfd_manager.cpp ../util/addr_util.cpp ../util/str_util.cpp ../util/json_util.cpp ../../json11/json11.cpp ) +target_link_libraries(test_cluster_client ${OPENSSL_LIBRARIES}) target_compile_definitions(test_cluster_client PUBLIC -D__MOCK__) target_include_directories(test_cluster_client BEFORE PUBLIC ${CMAKE_SOURCE_DIR}/src/test/mock) add_dependencies(build_tests test_cluster_client) diff --git a/src/client/cluster_client.cpp b/src/client/cluster_client.cpp index e7c54edb..3fa59ad1 100644 --- a/src/client/cluster_client.cpp +++ b/src/client/cluster_client.cpp @@ -958,9 +958,22 @@ bool cluster_client_t::check_rw(cluster_op_t *op) { op->flags |= OP_IMMEDIATE_COMMIT; } + auto ino_it = st_cli.inode_config.find(op->inode); + if (ino_it != st_cli.inode_config.end() && ino_it->second.enc) + { + // FIXME: Rework client API by adding open/close and cache inode information in the "FD" + op->enc = ino_it->second.enc; + if (!op->enc->bitmap_granularity) + { + op->enc->bitmap_granularity = pool_it->second.bitmap_granularity; + } + } + else + { + op->enc.reset(); + } if ((op->opcode == OSD_OP_WRITE || op->opcode == OSD_OP_DELETE) && !(op->flags & OSD_OP_IGNORE_READONLY)) { - auto ino_it = st_cli.inode_config.find(op->inode); if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly) { op->retval = -EROFS; @@ -972,7 +985,6 @@ bool cluster_client_t::check_rw(cluster_op_t *op) op->deoptimise_snapshot = false; if (enable_writeback && (op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP)) { - auto ino_it = st_cli.inode_config.find(op->inode); if (ino_it != st_cli.inode_config.end()) { int chain_size = 0; @@ -1435,6 +1447,7 @@ int cluster_client_t::try_send(cluster_op_t *op, int i, std::functionpart_bitmaps + pg_bitmap_size*i : NULL), .bitmap_len = (unsigned)(op->opcode == OSD_OP_READ || op->opcode == OSD_OP_READ_BITMAP || op->opcode == OSD_OP_READ_CHAIN_BITMAP ? pg_bitmap_size : 0), + .enc = op->enc, .callback = cb ? cb : [this, part](osd_op_t *op_part) { handle_op_part(part); diff --git a/src/client/cluster_client.h b/src/client/cluster_client.h index 23a8b05c..ed861d46 100644 --- a/src/client/cluster_client.h +++ b/src/client/cluster_client.h @@ -71,6 +71,7 @@ protected: cluster_op_t *prev = NULL, *next = NULL; int prev_wait = 0; uint64_t flush_id = 0; + std::shared_ptr enc; friend class cluster_client_t; friend class writeback_cache_t; }; diff --git a/src/client/etcd_state_client.cpp b/src/client/etcd_state_client.cpp index 0ca40596..409f8b3d 100644 --- a/src/client/etcd_state_client.cpp +++ b/src/client/etcd_state_client.cpp @@ -1296,6 +1296,16 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv) else parent_inode_num |= parent_pool_id << (64-POOL_ID_BITS); } + std::shared_ptr enc; + if (!value["enc_key"].string_value().empty()) + { + std::vector k = hexdecode(value["enc_key"].string_value()); + if (k.size() == 512/8) + { + enc = std::make_shared(); + enc->key = std::move(k); + } + } insert_inode_config((inode_config_t){ .num = inode_num, .name = value["name"].string_value(), @@ -1303,6 +1313,7 @@ void etcd_state_client_t::parse_state(const etcd_kv_t & kv) .parent_id = parent_inode_num, .readonly = value["readonly"].bool_value(), .deleted = value["deleted"].bool_value(), + .enc = enc, .meta = value["meta"], .mod_revision = kv.mod_revision, }); diff --git a/src/client/etcd_state_client.h b/src/client/etcd_state_client.h index 7f80400a..d24d4ed7 100644 --- a/src/client/etcd_state_client.h +++ b/src/client/etcd_state_client.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "json11/json11.hpp" #include "osd_id.h" @@ -75,6 +76,14 @@ struct pool_config_t void *reshard_state = NULL; }; +struct inode_enc_t +{ + int refs = 0; + std::vector key; + // FIXME It may also contain snapshot chain and key information + uint32_t bitmap_granularity = 0; +}; + struct inode_config_t { uint64_t num = 0; @@ -83,6 +92,7 @@ struct inode_config_t inode_t parent_id = 0; bool readonly = false; bool deleted = false; + std::shared_ptr enc; // Arbitrary metadata json11::Json meta; // Change revision of the metadata in etcd diff --git a/src/client/messenger.cpp b/src/client/messenger.cpp index 371707cf..2630c6d3 100644 --- a/src/client/messenger.cpp +++ b/src/client/messenger.cpp @@ -289,6 +289,16 @@ osd_messenger_t::~osd_messenger_t() rdmacm_evch = NULL; } #endif +#ifdef WITH_OPENSSL + for (auto encrypt_ctx: encrypt_ctx_pool) + { + destroy_aes_xts_encrypt(encrypt_ctx); + } + for (auto decrypt_ctx: decrypt_ctx_pool) + { + destroy_aes_xts_decrypt(decrypt_ctx); + } +#endif } void osd_messenger_t::parse_config(const json11::Json & config) @@ -323,6 +333,9 @@ void osd_messenger_t::parse_config(const json11::Json & config) if (!this->rdma_max_msg || this->rdma_max_msg > 128*1024*1024) this->rdma_max_msg = 129*1024; #endif + this->max_aes_xts_pool_size = config["max_aes_xts_pool_size"].uint64_value(); + if (!this->max_aes_xts_pool_size) + this->max_aes_xts_pool_size = 256; if (!osd_num) this->iothread_count = (uint32_t)config["client_iothread_count"].uint64_value(); else diff --git a/src/client/messenger.h b/src/client/messenger.h index d76924bc..ca881fae 100644 --- a/src/client/messenger.h +++ b/src/client/messenger.h @@ -45,6 +45,13 @@ struct msgr_rdma_connection_t; struct msgr_rdma_context_t; #endif +#ifdef WITH_OPENSSL +struct op_aes_xts_encrypt_t; +struct op_aes_xts_decrypt_t; +void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx); +void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx); +#endif + struct osd_client_t { int refs = 0; @@ -67,10 +74,12 @@ struct osd_client_t #endif // Read state + op_aes_xts_decrypt_t *decrypt_ctx = NULL; int read_ready = 0; osd_op_t *read_op = NULL; size_t read_op_size = 0; size_t read_op_pos = 0; + size_t read_op_inline_decrypt_pos = 0; iovec read_iov = { 0 }; msghdr read_msg = { 0 }; std::vector recv_list; @@ -90,6 +99,7 @@ struct osd_client_t std::set dirty_pgs; // Write state + op_aes_xts_encrypt_t *encrypt_ctx = NULL; std::deque write_ops; osd_op_t *write_op = NULL; size_t write_op_pos = 0; @@ -190,6 +200,7 @@ protected: bool use_sync_send_recv = false; int min_zerocopy_send_size = DEFAULT_MIN_ZEROCOPY_SEND_SIZE; int iothread_count = 0; + int max_aes_xts_pool_size = 256; #ifdef WITH_RDMA bool use_rdma = true; @@ -213,6 +224,11 @@ protected: // We don't use ringloop->set_immediate here because we may have no ringloop in client :) std::vector set_immediate_ops; +#ifdef WITH_OPENSSL + std::vector encrypt_ctx_pool; + std::vector decrypt_ctx_pool; +#endif + public: timerfd_manager_t *tfd = NULL; ring_loop_t *ringloop = NULL; @@ -276,6 +292,7 @@ protected: bool try_send(osd_client_t *cl); void handle_send(int result, bool prev, bool more, osd_client_t *cl); + bool op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done); size_t op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_len); void op_get_write_buffers(osd_client_t *cl, std::vector & lst); @@ -285,6 +302,10 @@ protected: bool allocate_op_buffers(osd_client_t *cl); bool allocate_reply_buffers(osd_client_t *cl, osd_op_t *op); size_t op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_len, size_t & done); + bool op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *buf, size_t len, size_t from, size_t & done); + void op_decrypt_start(osd_client_t* cl); + void op_decrypt_inline(osd_client_t* cl); + void op_decrypt_free(osd_client_t* cl); size_t op_get_read_buffers(osd_client_t *cl, std::vector & lst); void handle_finished_op(osd_client_t *cl); diff --git a/src/client/msgr_encrypt.cpp b/src/client/msgr_encrypt.cpp new file mode 100644 index 00000000..02803367 --- /dev/null +++ b/src/client/msgr_encrypt.cpp @@ -0,0 +1,328 @@ +// Copyright (c) Vitaliy Filippov, 2026+ +// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details) + +#define _XOPEN_SOURCE +#include +#include + +#include "etcd_state_client.h" +#include "messenger.h" +#include "msgr_encrypt.h" + +// FIXME Fuck, no streaming... + +op_aes_xts_encrypt_t::op_aes_xts_encrypt_t() +{ + if (!(ctx = EVP_CIPHER_CTX_new())) + { + ERR_print_errors_fp(stderr); + abort(); + } + EVP_CIPHER_CTX_set_padding(ctx, 0); + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } +} + +op_aes_xts_encrypt_t::~op_aes_xts_encrypt_t() +{ + EVP_CIPHER_CTX_free(ctx); +} + +void op_aes_xts_encrypt_t::start(const uint8_t *key, uint64_t start_offset, size_t block_size) +{ + this->start_offset = start_offset; + this->key = key; + this->block_size = block_size; + this->offset = 0; +} + +void op_aes_xts_encrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out) +{ + if (max_in > block_size - offset%block_size) + max_in = block_size - offset%block_size; + size_t insize = max_in; + size_t outsize = ((offset+insize)/16 - offset/16) * 16; + if (outsize > max_out) + { + // encrypt is used to send data through temporary buffer(s), + // so we don't care to support fragmenting output into < 16 b parts + insize = (max_out < 16 ? 0 : (max_out & ~15) - offset%16); + outsize = ((offset+insize)/16 - offset/16) * 16; + } + assert(insize <= max_in); + assert(outsize <= max_out); + if (!(offset % block_size)) + { + uint8_t iv[16] = { 0 }; + *((uint64_t*)iv) = start_offset + offset; + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } + } + int actual_out = 0; + if (EVP_EncryptUpdate(ctx, out, &actual_out, in, insize) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } + assert(actual_out == outsize); + done_in += insize; + done_out += outsize; + offset += insize; +} + +void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx) +{ + delete encrypt_ctx; +} + +op_aes_xts_decrypt_t::op_aes_xts_decrypt_t() +{ + if (!(ctx = EVP_CIPHER_CTX_new())) + { + ERR_print_errors_fp(stderr); + abort(); + } + EVP_CIPHER_CTX_set_padding(ctx, 0); + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_xts(), NULL, NULL, NULL) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } +} + +op_aes_xts_decrypt_t::~op_aes_xts_decrypt_t() +{ + EVP_CIPHER_CTX_free(ctx); +} + +void op_aes_xts_decrypt_t::start(const uint8_t *key, uint64_t start_offset, size_t block_size) +{ + this->start_offset = start_offset; + this->key = key; + this->block_size = block_size; + this->in_offset = 0; + this->tmp_pos = 16; +} + +void op_aes_xts_decrypt_t::update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out) +{ + if (max_in > block_size - in_offset%block_size) + max_in = block_size - in_offset%block_size; + int actual_out = 0; + // Write previously buffered block to support small output buffers + if (tmp_pos < 16) + { + size_t tmp_size = 16-tmp_pos; + if (tmp_size > max_out) + tmp_size = max_out; + memcpy(out, tmp_buf+tmp_pos, tmp_size); + tmp_pos += tmp_size; + done_out += tmp_size; + out += tmp_size; + max_out -= tmp_size; + if (!max_out) + return; + assert(tmp_pos == 16); + } + if (!(in_offset % block_size)) + { + uint8_t iv[16] = { 0 }; + *((uint64_t*)iv) = start_offset+in_offset; + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } + } + size_t insize = max_in; + size_t outsize = ((in_offset+insize)/16 - in_offset/16) * 16; + if (outsize > max_out) + { + if (max_out < 16) + { + // We can only decrypt a partial block + insize = 16 - in_offset%16; + if (EVP_DecryptUpdate(ctx, tmp_buf, &actual_out, in, insize) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } + assert(actual_out == 16); + in_offset += insize; + in += insize; + max_in -= insize; + tmp_pos = 0; + return; + } + // Otherwise, we can decrypt at least some data into directly + insize = (max_out & ~15) - (in_offset % 16); + outsize = (max_out & ~15); + assert(insize < max_in); + } + if (EVP_DecryptUpdate(ctx, out, &actual_out, in, insize) != 1) + { + ERR_print_errors_fp(stderr); + abort(); + } + assert(actual_out == outsize); + in_offset += insize; + done_in += insize; + done_out += actual_out; +} + +void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx) +{ + delete decrypt_ctx; +} + +bool osd_messenger_t::op_encrypted_copy_data_to(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done) +{ + auto op = cl->write_op; + auto & op_pos = cl->write_op_pos; + assert(op->req.hdr.opcode == OSD_OP_WRITE); + if (!from) + { + if (!cl->encrypt_ctx) + { + if (encrypt_ctx_pool.size()) + { + cl->encrypt_ctx = encrypt_ctx_pool.back(); + encrypt_ctx_pool.pop_back(); + } + else + cl->encrypt_ctx = new op_aes_xts_encrypt_t(); + } + assert(op->enc->key.size() == 512/8); + cl->encrypt_ctx->start(op->enc->key.data(), op->req.rw.offset, op->enc->bitmap_granularity); + } + for (int i = 0; i < op->iov.count; i++) + { + uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base; + size_t plain_len = op->iov.buf[i].iov_len; + while (from < plain_len) + { + size_t done_in = 0; + size_t done_out = 0; + cl->encrypt_ctx->update(plain+from, plain_len-from, enc_buf+done, enc_len-done, done_in, done_out); + done += done_out; + op_pos += done_in; + from += done_in; + if (!done_in) + return false; + } + from -= plain_len; + } + if (cl->encrypt_ctx) + { + if (encrypt_ctx_pool.size() > max_aes_xts_pool_size) + delete cl->encrypt_ctx; + else + encrypt_ctx_pool.push_back(cl->encrypt_ctx); + cl->encrypt_ctx = NULL; + } + return true; +} + +bool osd_messenger_t::op_decrypted_copy_data_from(osd_client_t* cl, uint8_t *enc_buf, size_t enc_len, size_t from, size_t & done) +{ + op_decrypt_start(cl); + auto op = cl->read_op; + auto & op_pos = cl->read_op_pos; + assert(op->req.hdr.opcode == OSD_OP_READ); + uint64_t offset = from; + for (int i = 0; i < op->iov.count; i++) + { + uint8_t *plain = (uint8_t*)op->iov.buf[i].iov_base; + size_t plain_len = op->iov.buf[i].iov_len; + while (from < plain_len) + { + size_t done_in = 0; + size_t done_out = 0; + cl->decrypt_ctx->update(enc_buf+done, enc_len-done, plain+from, plain_len-from, done_in, done_out); + done += done_in; + offset += done_in; + op_pos += done_out; + from += done_out; + if (!done_in) + return false; + } + from -= plain_len; + } + op_decrypt_free(cl); + return true; +} + +void osd_messenger_t::op_decrypt_start(osd_client_t* cl) +{ + if (!cl->decrypt_ctx) + { + if (decrypt_ctx_pool.size()) + { + cl->decrypt_ctx = decrypt_ctx_pool.back(); + decrypt_ctx_pool.pop_back(); + } + else + cl->decrypt_ctx = new op_aes_xts_decrypt_t(); + assert(cl->read_op->enc->key.size() == 512/8); + cl->decrypt_ctx->start(cl->read_op->enc->key.data(), cl->read_op->req.rw.offset, cl->read_op->enc->bitmap_granularity); + } +} + +void osd_messenger_t::op_decrypt_inline(osd_client_t* cl) +{ + op_decrypt_start(cl); + osd_op_t *op = cl->read_op; + size_t from_in = cl->read_op_inline_decrypt_pos - OSD_PACKET_SIZE - op->reply.rw.bitmap_len; + int i = 0; + while (i < op->iov.count && from_in >= op->iov.buf[i].iov_len) + { + from_in -= op->iov.buf[i].iov_len; + i++; + } + size_t from_out = from_in; + int j = i; + while (i < op->iov.count && j < op->iov.count) + { + uint8_t *in = (uint8_t*)op->iov.buf[i].iov_base + from_in; + size_t in_len = op->iov.buf[i].iov_len - from_in; + uint8_t *out = (uint8_t*)op->iov.buf[j].iov_base + from_out; + size_t out_len = op->iov.buf[j].iov_len - from_out; + size_t done_in = 0; + size_t done_out = 0; + cl->decrypt_ctx->update(in, in_len, out, out_len, done_in, done_out); + if (done_in >= in_len) + { + i++; + from_in = 0; + } + else + from_in += done_in; + if (done_out >= out_len) + { + j++; + from_out = 0; + } + else + from_out += done_out; + } + assert(j >= op->iov.count); + op_decrypt_free(cl); +} + +void osd_messenger_t::op_decrypt_free(osd_client_t* cl) +{ + if (cl->decrypt_ctx) + { + if (decrypt_ctx_pool.size() > max_aes_xts_pool_size) + delete cl->decrypt_ctx; + else + decrypt_ctx_pool.push_back(cl->decrypt_ctx); + cl->decrypt_ctx = NULL; + } +} diff --git a/src/client/msgr_encrypt.h b/src/client/msgr_encrypt.h new file mode 100644 index 00000000..859cb19f --- /dev/null +++ b/src/client/msgr_encrypt.h @@ -0,0 +1,44 @@ +// Copyright (c) Vitaliy Filippov, 2026+ +// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details) + +#include + +#include +#include +#include + +struct op_aes_xts_encrypt_t +{ + EVP_CIPHER_CTX *ctx = NULL; + uint64_t start_offset = 0; + const uint8_t *key = NULL; + size_t offset = 0; + size_t block_size = 0; + + op_aes_xts_encrypt_t(); + ~op_aes_xts_encrypt_t(); + + void start(const uint8_t *key, uint64_t start_offset, size_t block_size); + void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out); +}; + +void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx); + +struct op_aes_xts_decrypt_t +{ + EVP_CIPHER_CTX *ctx = NULL; + uint64_t start_offset = 0; + const uint8_t *key = NULL; + uint8_t tmp_buf[16]; + size_t tmp_pos = 16; + size_t in_offset = 0; + size_t block_size = 0; + + op_aes_xts_decrypt_t(); + ~op_aes_xts_decrypt_t(); + + void start(const uint8_t *key, uint64_t start_offset, size_t block_size); + void update(uint8_t *in, size_t max_in, uint8_t *out, size_t max_out, size_t & done_in, size_t & done_out); +}; + +void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx); diff --git a/src/client/msgr_op.cpp b/src/client/msgr_op.cpp index 84dd0a0f..d5e56383 100644 --- a/src/client/msgr_op.cpp +++ b/src/client/msgr_op.cpp @@ -23,6 +23,10 @@ osd_op_t::~osd_op_t() // So we don't reuse it, but free it every time free(buf); } + if (enc_buf) + { + free(enc_buf); + } } bool osd_op_t::is_recovery_related() diff --git a/src/client/msgr_op.h b/src/client/msgr_op.h index 61fd2429..00e739a2 100644 --- a/src/client/msgr_op.h +++ b/src/client/msgr_op.h @@ -3,6 +3,8 @@ #pragma once +#include + #include #include #include @@ -152,6 +154,8 @@ struct blockstore_op_t; struct osd_primary_op_data_t; +struct inode_enc_t; + struct __attribute__((visibility("default"))) osd_op_t { timespec tv_begin = { 0 }, tv_end = { 0 }; @@ -167,6 +171,8 @@ struct __attribute__((visibility("default"))) osd_op_t unsigned bmp_data = 0; void *bitmap_buf = NULL; void *rmw_buf = NULL; + std::shared_ptr enc; + uint8_t *enc_buf = NULL; osd_primary_op_data_t* op_data = NULL; std::function callback; diff --git a/src/client/msgr_receive.cpp b/src/client/msgr_receive.cpp index 642dc46b..75fecebd 100644 --- a/src/client/msgr_receive.cpp +++ b/src/client/msgr_receive.cpp @@ -206,6 +206,7 @@ bool osd_messenger_t::handle_read_buffer(osd_client_t *cl, uint8_t *curbuf, size cl->read_op->op_type = OSD_OP_IN; cl->read_op_pos = 0; cl->read_op_size = 0; + cl->read_op_inline_decrypt_pos = (size_t)-1; } if (cl->read_op_pos < OSD_PACKET_SIZE) { @@ -464,9 +465,17 @@ size_t osd_messenger_t::op_copy_from(osd_client_t *cl, uint8_t *src, size_t src_ } if (op->reply.hdr.retval > 0) { - for (int i = 0; i < op->iov.count; i++) - if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len)) + if (op->enc) + { + if (!op_decrypted_copy_data_from(cl, src, src_len, from, done)) return done; + } + else + { + for (int i = 0; i < op->iov.count; i++) + if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len)) + return done; + } } } else if (op->reply.hdr.opcode == OSD_OP_SEC_LIST && op->reply.hdr.retval > 0) @@ -567,6 +576,8 @@ size_t osd_messenger_t::op_get_read_buffers(osd_client_t *cl, std::vector } if (op->reply.hdr.retval > 0) { + if (op->enc) + cl->read_op_inline_decrypt_pos = cl->read_op_pos; for (int i = 0; i < op->iov.count; i++) if (!op_read_buf((uint8_t*)op->iov.buf[i].iov_base, op->iov.buf[i].iov_len)) return done; @@ -602,6 +613,12 @@ void osd_messenger_t::handle_finished_op(osd_client_t *cl) } else { + // Inline decryption + if (cl->read_op_inline_decrypt_pos != (size_t)-1) + { + op_decrypt_inline(cl); + cl->read_op_inline_decrypt_pos = (size_t)-1; + } // Measure subop (outbound op) latency timespec tv_end; clock_gettime(CLOCK_REALTIME, &tv_end); diff --git a/src/client/msgr_send.cpp b/src/client/msgr_send.cpp index a9a5c17b..0e72aa1b 100644 --- a/src/client/msgr_send.cpp +++ b/src/client/msgr_send.cpp @@ -5,12 +5,6 @@ #include #include -#ifdef WITH_OPENSSL -#include -#include -#include -#endif - #include "messenger.h" void osd_messenger_t::outbox_push(osd_op_t *cur_op) @@ -384,10 +378,20 @@ size_t osd_messenger_t::op_copy_to(osd_client_t *cl, uint8_t *dst, size_t dst_le // Operation data if (op_has_data(cl->write_op)) { - for (int i = 0; i < cl->write_op->iov.count; i++) + if (cl->write_op->enc) { - if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len)) + if (!op_encrypted_copy_data_to(cl, dst, dst_len, from, done)) + { return done; + } + } + else + { + for (int i = 0; i < cl->write_op->iov.count; i++) + { + if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len)) + return done; + } } } cl->write_op = NULL; @@ -419,10 +423,28 @@ void osd_messenger_t::op_get_write_buffers(osd_client_t *cl, std::vector // Operation data if (op_has_data(cl->write_op)) { - for (int i = 0; i < cl->write_op->iov.count; i++) + if (cl->write_op->enc) { - if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len)) + if (lst.size() >= IOV_MAX) return; + // No way except to allocate a temporary buffer and encrypt data to it + assert(cl->write_op->req.hdr.opcode == OSD_OP_WRITE); + size_t remsize = cl->write_op->req.rw.len - from + (from % 16); + assert(remsize > 0); + assert(!cl->write_op->enc_buf); + cl->write_op->enc_buf = (uint8_t*)malloc_or_die(remsize); + size_t done = 0; + bool end = op_encrypted_copy_data_to(cl, cl->write_op->enc_buf, remsize, from, done); + assert(end); + lst.push_back((iovec){ .iov_base = cl->write_op->enc_buf, .iov_len = remsize }); + } + else + { + for (int i = 0; i < cl->write_op->iov.count; i++) + { + if (!op_write_buf((uint8_t*)cl->write_op->iov.buf[i].iov_base, cl->write_op->iov.buf[i].iov_len)) + return; + } } } cl->write_op = NULL; diff --git a/src/client/msgr_stop.cpp b/src/client/msgr_stop.cpp index ec9ac1aa..9701e6f2 100644 --- a/src/client/msgr_stop.cpp +++ b/src/client/msgr_stop.cpp @@ -72,6 +72,22 @@ void osd_messenger_t::stop_client(int peer_fd, bool force, bool force_delete) fprintf(stderr, "[OSD %ju] Stopping client %d (regular client)\n", osd_num, peer_fd); } } + if (cl->encrypt_ctx) + { + if (encrypt_ctx_pool.size() > max_aes_xts_pool_size) + destroy_aes_xts_encrypt(cl->encrypt_ctx); + else + encrypt_ctx_pool.push_back(cl->encrypt_ctx); + cl->encrypt_ctx = NULL; + } + if (cl->decrypt_ctx) + { + if (decrypt_ctx_pool.size() > max_aes_xts_pool_size) + destroy_aes_xts_decrypt(cl->decrypt_ctx); + else + decrypt_ctx_pool.push_back(cl->decrypt_ctx); + cl->decrypt_ctx = NULL; + } // First set state to STOPPED so another stop_client() call doesn't try to free it again cl->refs++; int prev_state = cl->peer_state; diff --git a/src/test/test_cluster_client.cpp b/src/test/test_cluster_client.cpp index e3fd91e3..45dbe17b 100644 --- a/src/test/test_cluster_client.cpp +++ b/src/test/test_cluster_client.cpp @@ -1,10 +1,15 @@ // Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) +#ifdef WITH_OPENSSL +#include +#endif + #include #include #include #include "cluster_client_impl.h" +#include "msgr_encrypt.h" void configure_single_pg_pool(cluster_client_t *cli) { @@ -547,11 +552,76 @@ void test_writeback_merge() printf("[ok] writeback merge test\n"); } +#ifdef WITH_OPENSSL +void test_msgr_encrypt() +{ + const size_t sz = 1048576; + uint8_t *src = (uint8_t*)malloc_or_die(sz); + for (size_t i = 0; i < sz; i++) + src[i] = (i*0x1001) % 256; + uint8_t *crypt = (uint8_t*)malloc_or_die(sz); + uint8_t *decrypt = (uint8_t*)malloc_or_die(sz); + uint8_t *crypt2 = (uint8_t*)malloc_or_die(sz); + uint8_t *key = (uint8_t*)malloc_or_die(64); + RAND_bytes(key, 64); + + // Basic encrypt+decrypt and also get reference data + + auto enc = new op_aes_xts_encrypt_t(); + enc->start(key, 4096 * 113, 4096); + size_t in_pos = 0; + size_t out_pos = 0; + while (out_pos < sz) + { + enc->update(src+in_pos, sz-in_pos, crypt+out_pos, sz-out_pos, in_pos, out_pos); + } + + auto dec = new op_aes_xts_decrypt_t(); + dec->start(key, 4096 * 113, 4096); + in_pos = out_pos = 0; + while (out_pos < sz) + { + dec->update(crypt+in_pos, sz-in_pos, decrypt+out_pos, sz-out_pos, in_pos, out_pos); + } + + assert(memcmp(src, decrypt, sz) == 0); + + // 4095+1 byte fragmented encrypt + + enc->start(key, 4096 * 114, 4096); + in_pos = out_pos = 0; + enc->update(src+4096, 4096, crypt2, 4095, in_pos, out_pos); + assert(in_pos == 4080); + assert(out_pos == 4080); + enc->update(src+4096+in_pos, 4096-in_pos, crypt2+out_pos, 4096-out_pos, in_pos, out_pos); + assert(in_pos == 4096); + assert(out_pos == 4096); + assert(memcmp(crypt2, crypt+4096, 4096) == 0); + + // FIXME 5+27+... fragmented encrypt + + // FIXME Fragmented decrypt with small buffers + + delete dec; + delete enc; + + free(key); + free(crypt2); + free(decrypt); + free(crypt); + free(src); + printf("[ok] msgr aes-xts encryption test\n"); +} +#endif + int main(int narg, char *args[]) { test1(); test2(); test_writeback(); test_writeback_merge(); +#ifdef WITH_OPENSSL + test_msgr_encrypt(); +#endif return 0; }