From: Or Ozeri Date: Thu, 10 Sep 2020 08:51:56 +0000 (+0300) Subject: librbd: add data encryptor/decryptor using openssl X-Git-Tag: v17.0.0~1043^2 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=2f4c769e67b895b2d56a5899e892c8b64893b78e;p=ceph.git librbd: add data encryptor/decryptor using openssl This commit adds the first CryptoInterface implementation which translates bufferlist encryption to the standard api of crypto libraries, i.e. using block ciphers operating on C-style arrays. It defines an abstract DataCryptor interface to allow different crypto libraries implementations. With this commit, we add an OpenSSL based implementation. Lastly, we implement a context-pool wrapper implementation to optimize performance. Signed-off-by: Or Ozeri --- diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index b73fa25b76224..9a570bcfbb9a6 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -46,7 +46,10 @@ set(librbd_internal_srcs cache/rwl/InitRequest.cc cache/rwl/ShutdownRequest.cc cache/WriteAroundObjectDispatch.cc + crypto/BlockCrypto.cc + crypto/CryptoContextPool.cc crypto/CryptoObjectDispatch.cc + crypto/openssl/DataCryptor.cc deep_copy/ImageCopyRequest.cc deep_copy/MetadataCopyRequest.cc deep_copy/ObjectCopyRequest.cc @@ -207,6 +210,7 @@ if(WITH_EVENTTRACE) endif() target_link_libraries(rbd_internal PRIVATE osdc rbd_types) +target_include_directories(rbd_internal PRIVATE ${OPENSSL_INCLUDE_DIR}) if(WITH_RBD_RWL) target_link_libraries(rbd_internal diff --git a/src/librbd/crypto/BlockCrypto.cc b/src/librbd/crypto/BlockCrypto.cc new file mode 100644 index 0000000000000..00b244c47a5d8 --- /dev/null +++ b/src/librbd/crypto/BlockCrypto.cc @@ -0,0 +1,100 @@ +// -*- 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 "include/byteorder.h" +#include "include/ceph_assert.h" + +namespace librbd { +namespace crypto { + +template +BlockCrypto::BlockCrypto(CephContext* cct, DataCryptor* data_cryptor, + uint32_t block_size) + : m_cct(cct), m_data_cryptor(data_cryptor), m_block_size(block_size), + m_iv_size(data_cryptor->get_iv_size()) { + ceph_assert((block_size % data_cryptor->get_block_size()) == 0); +} + +template +int BlockCrypto::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 block_offset = image_offset / m_block_size; + auto appender = data->get_contiguous_appender(src.length()); + unsigned char* out_buf_ptr = nullptr; + uint32_t remaining_block_bytes = 0; + for (auto buf = src.buffers().begin(); buf != src.buffers().end(); ++buf) { + auto in_buf_ptr = reinterpret_cast(buf->c_str()); + auto remaining_buf_bytes = buf->length(); + while (remaining_buf_bytes > 0) { + if (remaining_block_bytes == 0) { + auto block_offset_le = init_le64(block_offset); + 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( + appender.get_pos_add(m_block_size)); + remaining_block_bytes = m_block_size; + ++block_offset; + } + + auto crypto_input_length = std::min(remaining_buf_bytes, + remaining_block_bytes); + auto crypto_output_length = m_data_cryptor->update_context( + ctx, in_buf_ptr, out_buf_ptr, crypto_input_length); + if (crypto_output_length < 0) { + lderr(m_cct) << "crypt update failed" << dendl; + return crypto_output_length; + } + + out_buf_ptr += crypto_output_length; + in_buf_ptr += crypto_input_length; + remaining_buf_bytes -= crypto_input_length; + remaining_block_bytes -= crypto_input_length; + } + } + + m_data_cryptor->return_context(ctx, mode); + + return 0; +} + +template +int BlockCrypto::encrypt(ceph::bufferlist* data, uint64_t image_offset) { + return crypt(data, image_offset, CipherMode::CIPHER_MODE_ENC); +} + +template +int BlockCrypto::decrypt(ceph::bufferlist* data, uint64_t image_offset) { + return crypt(data, image_offset, CipherMode::CIPHER_MODE_DEC); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/librbd/crypto/BlockCrypto.h b/src/librbd/crypto/BlockCrypto.h new file mode 100644 index 0000000000000..fa51a6192e255 --- /dev/null +++ b/src/librbd/crypto/BlockCrypto.h @@ -0,0 +1,40 @@ +// -*- 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/DataCryptor.h" + +namespace librbd { +namespace crypto { + +template +class BlockCrypto : public CryptoInterface { + +public: + BlockCrypto(CephContext* cct, DataCryptor* data_cryptor, + uint32_t block_size); + + int encrypt(ceph::bufferlist* data, uint64_t image_offset) override; + int decrypt(ceph::bufferlist* data, uint64_t image_offset) override; + + uint32_t get_block_size() const { + return m_block_size; + } + +private: + CephContext* m_cct; + DataCryptor* m_data_cryptor; + uint32_t m_block_size; + uint32_t m_iv_size; + + int crypt(ceph::bufferlist* data, uint64_t image_offset, CipherMode mode); +}; + +} // namespace crypto +} // namespace librbd + +#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 0000000000000..b303a54ec89d8 --- /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 +CryptoContextPool::CryptoContextPool(DataCryptor* data_cryptor, + uint32_t pool_size) + : m_data_cryptor(data_cryptor), m_encrypt_contexts(pool_size), + m_decrypt_contexts(pool_size) { +} + +template +CryptoContextPool::~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 +T* CryptoContextPool::get_context(CipherMode mode) { + T* ctx; + if (!get_contexts(mode).pop(ctx)) { + ctx = m_data_cryptor->get_context(mode); + } + return ctx; +} + +template +void CryptoContextPool::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 0000000000000..ce5abfa2ef4ef --- /dev/null +++ b/src/librbd/crypto/CryptoContextPool.h @@ -0,0 +1,65 @@ +// -*- 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 + +namespace librbd { +namespace crypto { + +template +class CryptoContextPool : public DataCryptor { + +public: + CryptoContextPool(DataCryptor* 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 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>> ContextQueue; + +private: + DataCryptor* 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/CryptoInterface.h b/src/librbd/crypto/CryptoInterface.h index 28f2baacc3894..e0e4f9f56272b 100644 --- a/src/librbd/crypto/CryptoInterface.h +++ b/src/librbd/crypto/CryptoInterface.h @@ -13,10 +13,8 @@ namespace crypto { class CryptoInterface : public RefCountedObject { public: - virtual void encrypt(ceph::bufferlist&& data, - uint64_t image_offset) const = 0; - virtual void decrypt(ceph::bufferlist&& data, - uint64_t image_offset) const = 0; + virtual int encrypt(ceph::bufferlist* data, uint64_t image_offset) = 0; + virtual int decrypt(ceph::bufferlist* data, uint64_t image_offset) = 0; }; diff --git a/src/librbd/crypto/CryptoObjectDispatch.cc b/src/librbd/crypto/CryptoObjectDispatch.cc index 12cc7e899ad65..eaad8dea6e066 100644 --- a/src/librbd/crypto/CryptoObjectDispatch.cc +++ b/src/librbd/crypto/CryptoObjectDispatch.cc @@ -72,11 +72,15 @@ struct C_EncryptedObjectReadRequest : public Context { void finish(int r) override { ldout(image_ctx->cct, 20) << "r=" << r << dendl; if (r > 0) { - crypto->decrypt( - std::move(*read_data), + auto crypto_ret = crypto->decrypt( + read_data, Striper::get_file_offset( image_ctx->cct, &image_ctx->layout, object_no, object_off)); + if (crypto_ret != 0) { + ceph_assert(crypto_ret < 0); + r = crypto_ret; + } } onfinish->complete(r); } @@ -151,13 +155,13 @@ bool CryptoObjectDispatch::write( << object_off << "~" << data.length() << dendl; ceph_assert(m_crypto != nullptr); - m_crypto->encrypt( - std::move(data), + auto r = m_crypto->encrypt( + &data, Striper::get_file_offset( m_image_ctx->cct, &m_image_ctx->layout, object_no, object_off)); *dispatch_result = io::DISPATCH_RESULT_CONTINUE; - on_dispatched->complete(0); + on_dispatched->complete(r); return true; } @@ -211,10 +215,12 @@ bool CryptoObjectDispatch::compare_and_write( uint64_t image_offset = Striper::get_file_offset( m_image_ctx->cct, &m_image_ctx->layout, object_no, object_off); - m_crypto->encrypt(std::move(cmp_data), image_offset); - m_crypto->encrypt(std::move(write_data), image_offset); + auto r = m_crypto->encrypt(&cmp_data, image_offset); + if (r == 0) { + r = m_crypto->encrypt(&write_data, image_offset); + } *dispatch_result = io::DISPATCH_RESULT_CONTINUE; - on_dispatched->complete(0); + on_dispatched->complete(r); return true; } diff --git a/src/librbd/crypto/DataCryptor.h b/src/librbd/crypto/DataCryptor.h new file mode 100644 index 0000000000000..cf293858907ba --- /dev/null +++ b/src/librbd/crypto/DataCryptor.h @@ -0,0 +1,34 @@ +// -*- 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 +class DataCryptor { + +public: + + virtual ~DataCryptor() = default; + + virtual uint32_t get_block_size() const = 0; + virtual uint32_t get_iv_size() 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/Types.h b/src/librbd/crypto/Types.h new file mode 100644 index 0000000000000..93d9c172c0622 --- /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/openssl/DataCryptor.cc b/src/librbd/crypto/openssl/DataCryptor.cc new file mode 100644 index 0000000000000..4394b17855b89 --- /dev/null +++ b/src/librbd/crypto/openssl/DataCryptor.cc @@ -0,0 +1,137 @@ +// -*- 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 +#include +#include "include/ceph_assert.h" + +namespace librbd { +namespace crypto { +namespace openssl { + +int DataCryptor::init(const char* cipher_name, const unsigned char* key, + uint16_t key_length) { + m_key = nullptr; + 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 = new unsigned char[key_length]; + memcpy(m_key, key, key_length); + m_iv_size = static_cast(EVP_CIPHER_iv_length(m_cipher)); + return 0; +} + +DataCryptor::~DataCryptor() { + if (m_key != nullptr) { + explicit_bzero(m_key, EVP_CIPHER_key_length(m_cipher)); + 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; +} + +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: " << error << 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 0000000000000..0c2795ef63e11 --- /dev/null +++ b/src/librbd/crypto/openssl/DataCryptor.h @@ -0,0 +1,45 @@ +// -*- 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 + +namespace librbd { +namespace crypto { +namespace openssl { + +class DataCryptor : public crypto::DataCryptor { + +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; + 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; + 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 diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index ae6966d5daf95..0230e2e2918fe 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -53,7 +53,10 @@ set(unittest_librbd_srcs test_mock_Watcher.cc cache/test_mock_WriteAroundObjectDispatch.cc cache/test_mock_ParentCacheObjectDispatch.cc + crypto/test_mock_BlockCrypto.cc + crypto/test_mock_CryptoContextPool.cc crypto/test_mock_CryptoObjectDispatch.cc + crypto/openssl/test_DataCryptor.cc deep_copy/test_mock_ImageCopyRequest.cc deep_copy/test_mock_MetadataCopyRequest.cc deep_copy/test_mock_ObjectCopyRequest.cc diff --git a/src/test/librbd/crypto/openssl/test_DataCryptor.cc b/src/test/librbd/crypto/openssl/test_DataCryptor.cc new file mode 100644 index 0000000000000..16c144e4a3b3e --- /dev/null +++ b/src/test/librbd/crypto/openssl/test_DataCryptor.cc @@ -0,0 +1,117 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "librbd/crypto/openssl/DataCryptor.h" + +namespace librbd { +namespace crypto { +namespace openssl { + +const char* TEST_CIPHER_NAME = "aes-256-xts"; +const unsigned char TEST_KEY[64] = {1}; +const unsigned char TEST_IV[16] = {2}; +const unsigned char TEST_IV_2[16] = {3}; +const unsigned char TEST_DATA[4096] = {4}; + +struct TestDataCryptor : public TestFixture { + DataCryptor *cryptor; + + void SetUp() override { + TestFixture::SetUp(); + cryptor = new DataCryptor(reinterpret_cast(m_ioctx.cct())); + ASSERT_EQ(0, + cryptor->init(TEST_CIPHER_NAME, TEST_KEY, sizeof(TEST_KEY))); + } + + void TearDown() override { + delete cryptor; + Test::TearDown(); + } +}; + +TEST_F(TestDataCryptor, InvalidCipherName) { + EXPECT_EQ(-EINVAL, cryptor->init(nullptr, TEST_KEY, sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init("", TEST_KEY, sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init("Invalid", TEST_KEY, sizeof(TEST_KEY))); +} + +TEST_F(TestDataCryptor, InvalidKey) { + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, 0)); + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, nullptr, + sizeof(TEST_KEY))); + EXPECT_EQ(-EINVAL, cryptor->init(TEST_CIPHER_NAME, TEST_KEY, 1)); +} + +TEST_F(TestDataCryptor, GetContextInvalidMode) { + EXPECT_EQ(nullptr, cryptor->get_context(static_cast(-1))); +} + +TEST_F(TestDataCryptor, ReturnNullContext) { + cryptor->return_context(nullptr, static_cast(-1)); +} + +TEST_F(TestDataCryptor, ReturnContextInvalidMode) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC); + ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->return_context(ctx, static_cast(-1)); +} + +TEST_F(TestDataCryptor, EncryptDecrypt) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV)); + + unsigned char out[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC); + ctx = cryptor->get_context(CipherMode::CIPHER_MODE_DEC); + ASSERT_NE(ctx, nullptr); + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV))); + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, out, out, sizeof(TEST_DATA))); + ASSERT_EQ(0, memcmp(out, TEST_DATA, sizeof(TEST_DATA))); + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_DEC); +} + +TEST_F(TestDataCryptor, ReuseContext) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV, sizeof(TEST_IV))); + unsigned char out[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + + ASSERT_EQ(0, cryptor->init_context(ctx, TEST_IV_2, sizeof(TEST_IV_2))); + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx, TEST_DATA, out, sizeof(TEST_DATA))); + + auto ctx2 = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx2, nullptr); + + ASSERT_EQ(0, cryptor->init_context(ctx2, TEST_IV_2, sizeof(TEST_IV_2))); + unsigned char out2[sizeof(TEST_DATA)]; + ASSERT_EQ(sizeof(TEST_DATA), + cryptor->update_context(ctx2, TEST_DATA, out2, sizeof(TEST_DATA))); + + ASSERT_EQ(0, memcmp(out, out2, sizeof(TEST_DATA))); + + cryptor->return_context(ctx, CipherMode::CIPHER_MODE_ENC); + cryptor->return_context(ctx2, CipherMode::CIPHER_MODE_ENC); +} + +TEST_F(TestDataCryptor, InvalidIVLength) { + auto ctx = cryptor->get_context(CipherMode::CIPHER_MODE_ENC); + ASSERT_NE(ctx, nullptr); + + ASSERT_EQ(-EINVAL, cryptor->init_context(ctx, TEST_IV, 1)); +} + +} // namespace openssl +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_BlockCrypto.cc b/src/test/librbd/crypto/test_mock_BlockCrypto.cc new file mode 100644 index 0000000000000..725101658503a --- /dev/null +++ b/src/test/librbd/crypto/test_mock_BlockCrypto.cc @@ -0,0 +1,144 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_fixture.h" +#include "librbd/crypto/BlockCrypto.h" +#include "test/librbd/mock/crypto/MockDataCryptor.h" + +#include "librbd/crypto/BlockCrypto.cc" +template class librbd::crypto::BlockCrypto< + librbd::crypto::MockCryptoContext>; + +using ::testing::ExpectationSet; +using ::testing::internal::ExpectationBase; +using ::testing::Return; +using ::testing::_; + +namespace librbd { +namespace crypto { + +MATCHER_P(CompareArrayToString, s, "") { + return (memcmp(arg, s.c_str(), s.length()) == 0); +} + +struct TestMockBlockCrypto : public TestFixture { + MockDataCryptor cryptor; + BlockCrypto* bc; + int cryptor_block_size = 2; + int cryptor_iv_size = 16; + int block_size = 4; + ExpectationSet* expectation_set; + + void SetUp() override { + TestFixture::SetUp(); + + cryptor.block_size = cryptor_block_size; + bc = new BlockCrypto( + reinterpret_cast(m_ioctx.cct()), &cryptor, + block_size); + expectation_set = new ExpectationSet(); + } + + void TearDown() override { + delete expectation_set; + TestFixture::TearDown(); + } + + void expect_get_context(CipherMode mode) { + _set_last_expectation( + EXPECT_CALL(cryptor, get_context(mode)) + .After(*expectation_set).WillOnce(Return( + new MockCryptoContext()))); + } + + void expect_init_context(const std::string& iv) { + _set_last_expectation( + EXPECT_CALL(cryptor, init_context(_, CompareArrayToString(iv), + cryptor_iv_size)) + .After(*expectation_set)); + } + + void expect_update_context(const std::string& in_str, int out_ret) { + _set_last_expectation( + EXPECT_CALL(cryptor, update_context(_, + CompareArrayToString(in_str), + _, in_str.length())) + .After(*expectation_set).WillOnce(Return(out_ret))); + } + + void _set_last_expectation(ExpectationBase& expectation) { + delete expectation_set; + expectation_set = new ExpectationSet(expectation); + } +}; + +TEST_F(TestMockBlockCrypto, Encrypt) { + uint32_t image_offset = 0x1234 * block_size; + + ceph::bufferlist data1; + data1.append("123"); + ceph::bufferlist data2; + data2.append("456"); + ceph::bufferlist data3; + data3.append("78"); + + // bufferlist buffers: "123", "456", "78" + ceph::bufferlist data; + data.claim_append(data1); + data.claim_append(data2); + data.claim_append(data3); + + expect_get_context(CipherMode::CIPHER_MODE_ENC); + expect_init_context(std::string("\x34\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)); + expect_update_context("123", 0); + expect_update_context("4", 4); + expect_init_context(std::string("\x35\x12\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)); + expect_update_context("56", 0); + expect_update_context("78", 4); + EXPECT_CALL(cryptor, return_context(_, CipherMode::CIPHER_MODE_ENC)); + + ASSERT_EQ(0, bc->encrypt(&data, image_offset)); + + ASSERT_EQ(data.length(), 8); + ASSERT_TRUE(data.is_aligned(block_size)); +} + +TEST_F(TestMockBlockCrypto, UnalignedImageOffset) { + ceph::bufferlist data; + data.append("1234"); + ASSERT_EQ(-EINVAL, bc->encrypt(&data, 2)); +} + +TEST_F(TestMockBlockCrypto, UnalignedDataLength) { + ceph::bufferlist data; + data.append("123"); + ASSERT_EQ(-EINVAL, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockBlockCrypto, GetContextError) { + ceph::bufferlist data; + data.append("1234"); + EXPECT_CALL(cryptor, get_context(CipherMode::CIPHER_MODE_ENC)).WillOnce( + Return(nullptr)); + ASSERT_EQ(-EIO, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockBlockCrypto, InitContextError) { + ceph::bufferlist data; + data.append("1234"); + expect_get_context(CipherMode::CIPHER_MODE_ENC); + EXPECT_CALL(cryptor, init_context(_, _, _)).WillOnce(Return(-123)); + ASSERT_EQ(-123, bc->encrypt(&data, 0)); +} + +TEST_F(TestMockBlockCrypto, UpdateContextError) { + ceph::bufferlist data; + data.append("1234"); + expect_get_context(CipherMode::CIPHER_MODE_ENC); + EXPECT_CALL(cryptor, init_context(_, _, _)); + EXPECT_CALL(cryptor, update_context(_, _, _, _)).WillOnce(Return(-123)); + ASSERT_EQ(-123, bc->encrypt(&data, 0)); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/test_mock_CryptoContextPool.cc b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc new file mode 100644 index 0000000000000..1d0402dd7b0e0 --- /dev/null +++ b/src/test/librbd/crypto/test_mock_CryptoContextPool.cc @@ -0,0 +1,54 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" +#include "librbd/crypto/CryptoContextPool.h" +#include "test/librbd/mock/crypto/MockDataCryptor.h" + +#include "librbd/crypto/CryptoContextPool.cc" +template class librbd::crypto::CryptoContextPool< + librbd::crypto::MockCryptoContext>; + +using ::testing::Return; + +namespace librbd { +namespace crypto { + +struct TestMockCryptoContextPool : public ::testing::Test { + MockDataCryptor cryptor; + + void expect_get_context(CipherMode mode) { + EXPECT_CALL(cryptor, get_context(mode)).WillOnce(Return( + new MockCryptoContext())); + } + + void expect_return_context(MockCryptoContext* ctx, CipherMode mode) { + delete ctx; + EXPECT_CALL(cryptor, return_context(ctx, mode)); + } +}; + +TEST_F(TestMockCryptoContextPool, Test) { + CryptoContextPool pool(&cryptor, 1); + + expect_get_context(CipherMode::CIPHER_MODE_ENC); + auto enc_ctx = pool.get_context(CipherMode::CIPHER_MODE_ENC); + + expect_get_context(CipherMode::CIPHER_MODE_DEC); + auto dec_ctx1 = pool.get_context(CipherMode::CIPHER_MODE_DEC); + expect_get_context(CipherMode::CIPHER_MODE_DEC); + auto dec_ctx2 = pool.get_context(CipherMode::CIPHER_MODE_DEC); + pool.return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC); + expect_return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC); + pool.return_context(dec_ctx2, CipherMode::CIPHER_MODE_DEC); + + pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + ASSERT_EQ(enc_ctx, pool.get_context(CipherMode::CIPHER_MODE_ENC)); + pool.return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + + expect_return_context(enc_ctx, CipherMode::CIPHER_MODE_ENC); + expect_return_context(dec_ctx1, CipherMode::CIPHER_MODE_DEC); +} + +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/mock/crypto/MockCryptoInterface.h b/src/test/librbd/mock/crypto/MockCryptoInterface.h index 5bb1c7e31c8b0..7c5869226d925 100644 --- a/src/test/librbd/mock/crypto/MockCryptoInterface.h +++ b/src/test/librbd/mock/crypto/MockCryptoInterface.h @@ -13,8 +13,8 @@ namespace crypto { struct MockCryptoInterface : CryptoInterface { - MOCK_CONST_METHOD2(encrypt, void(ceph::bufferlist&&, uint64_t)); - MOCK_CONST_METHOD2(decrypt, void(ceph::bufferlist&&, uint64_t)); + MOCK_METHOD2(encrypt, int(ceph::bufferlist*, uint64_t)); + MOCK_METHOD2(decrypt, int(ceph::bufferlist*, uint64_t)); }; } // namespace crypto diff --git a/src/test/librbd/mock/crypto/MockDataCryptor.h b/src/test/librbd/mock/crypto/MockDataCryptor.h new file mode 100644 index 0000000000000..2f0198b5a136f --- /dev/null +++ b/src/test/librbd/mock/crypto/MockDataCryptor.h @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H +#define CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H + +#include "gmock/gmock.h" +#include "librbd/crypto/DataCryptor.h" + +namespace librbd { +namespace crypto { + +struct MockCryptoContext {}; + +class MockDataCryptor : public DataCryptor { + +public: + uint32_t block_size = 16; + uint32_t iv_size = 16; + + uint32_t get_block_size() const override { + return block_size; + } + + uint32_t get_iv_size() const override { + return iv_size; + } + + MOCK_METHOD1(get_context, MockCryptoContext*(CipherMode)); + MOCK_METHOD2(return_context, void(MockCryptoContext*, CipherMode)); + MOCK_CONST_METHOD3(init_context, int(MockCryptoContext*, + const unsigned char*, uint32_t)); + MOCK_CONST_METHOD4(update_context, int(MockCryptoContext*, + const unsigned char*, unsigned char*, + uint32_t)); +}; + +} // namespace crypto +} // namespace librbd + +#endif // CEPH_TEST_LIBRBD_MOCK_CRYPTO_MOCK_DATA_CRYPTOR_H