WIP Add AES-XTS client encryption support
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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::function<void(osd_o
|
||||
? (uint8_t*)op->part_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);
|
||||
|
||||
@@ -71,6 +71,7 @@ protected:
|
||||
cluster_op_t *prev = NULL, *next = NULL;
|
||||
int prev_wait = 0;
|
||||
uint64_t flush_id = 0;
|
||||
std::shared_ptr<inode_enc_t> enc;
|
||||
friend class cluster_client_t;
|
||||
friend class writeback_cache_t;
|
||||
};
|
||||
|
||||
@@ -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<inode_enc_t> enc;
|
||||
if (!value["enc_key"].string_value().empty())
|
||||
{
|
||||
std::vector<uint8_t> k = hexdecode(value["enc_key"].string_value());
|
||||
if (k.size() == 512/8)
|
||||
{
|
||||
enc = std::make_shared<inode_enc_t>();
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
|
||||
#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<uint8_t> 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<inode_enc_t> enc;
|
||||
// Arbitrary metadata
|
||||
json11::Json meta;
|
||||
// Change revision of the metadata in etcd
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<iovec> recv_list;
|
||||
@@ -90,6 +99,7 @@ struct osd_client_t
|
||||
std::set<pool_pg_num_t> dirty_pgs;
|
||||
|
||||
// Write state
|
||||
op_aes_xts_encrypt_t *encrypt_ctx = NULL;
|
||||
std::deque<osd_op_t *> 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<osd_op_t*> set_immediate_ops;
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
std::vector<op_aes_xts_encrypt_t*> encrypt_ctx_pool;
|
||||
std::vector<op_aes_xts_decrypt_t*> 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<iovec> & 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<iovec> & lst);
|
||||
void handle_finished_op(osd_client_t *cl);
|
||||
|
||||
|
||||
@@ -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 <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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 <out> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2026+
|
||||
// License: VNPL-1.1 or GNU GPL-2.0+ (see README.md for details)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
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);
|
||||
@@ -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()
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <sys/uio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -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<inode_enc_t> enc;
|
||||
uint8_t *enc_buf = NULL;
|
||||
osd_primary_op_data_t* op_data = NULL;
|
||||
std::function<void(osd_op_t*)> callback;
|
||||
|
||||
|
||||
@@ -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<iovec>
|
||||
}
|
||||
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);
|
||||
|
||||
+32
-10
@@ -5,12 +5,6 @@
|
||||
#include <limits.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/err.h>
|
||||
#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<iovec>
|
||||
// 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
// Copyright (c) Vitaliy Filippov, 2019+
|
||||
// License: VNPL-1.1 (see README.md for details)
|
||||
|
||||
#ifdef WITH_OPENSSL
|
||||
#include <openssl/rand.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user